Si ce n'est pas encore fait, lisez « C vrai, C pas vrai » avant de lire la suite de cette page.
Ce programme écrit en langage C vérifie si le texte qu'on lui soumet est un bivocalisme alterné en A-I. Le programme satisfait lui-même cette contrainte.
C vrai, C pas vrai est un exemple d'application à un programme informatique d'une contrainte formelle habituellement réservée à des textes plus « littéraires ». Les considérations qui suivent n'intéresseront probablement que les personnes connaissant la programmation et en particulier le langage C.
Mon intention première était d'écrire un
programme monovocalique. Il est
malheureusement évident que ce n'est pas possible en C
puisque tout programme doit contenir une fonction appelée
main
. La plus forte restriction que l'on puisse imposer
sur les voyelles est donc un bivocalisme en A et
I. Pour augmenter un peu la difficulté, et en
référence à un autre
texte de ce site, j'ai choisi d'en faire un bivocalisme
alterné. Il paraissait naturel que la fonction du
programme soit précisément de vérifier si le
texte qu'il lit sur son entrée standard satisfait ou non
cette même contrainte. Le titre reflète ces deux
possibilités : « C vrai » respecte la
contrainte tandis que « C pas vrai » ne la
respecte pas.
Un programme C bivocalique en A et I est soumis à un grand nombre de restrictions, en particulier :
for
, while
, do/while
,
switch/case
ou goto
. Pour écrire
des alternatives, il ne reste que if
(sans else
, ce
qui impose souvent d'effectuer des tests redondants) ou les
expressions conditionnelles ...?
...:
... Pour effectuer des répétitions aucune
structure itérative n'est utilisable, il faut recourir
à la récursion.
char
et int
, ce qui suffit dans un grand
nombre de cas mais interdit notamment tous les struct
.
scanf
et printf
qui permettent d'effectuer
des lectures et écritures sur l'entrée et la sortie
standard. En revanche on ne peut pas lire ou écrire dans des
fichiers, faute de open
ou équivalent.
return
(comme d'ailleurs exit
)
est interdit.
#include
et #define
sont inutilisables, en particulier le classique
#include <stdio.h>
. Par chance, les seules
fonctions que nous utilisons étant scanf
et
printf
qui renvoient toutes les deux un int,
on peut se contenter de la déclaration implicite de ces
fonctions sans avoir besoin de la déclaration explicite
contenue dans stdio.h
.
En contrepartie, on peut choisir librement le nom des variables et, dans une certaine mesure, le contenu des chaînes de caractères, pour insérer des A ou des I là où on le souhaite. On pourrait également introduire des commentaires, mais je me suis refusé à user de cette facilité.
Compte tenu de ces contraintes, l'algorithme utilisé par le programme est le suivant :
On trouvera une analyse détaillée de toutes les instructions du programme dans la section Références.
Le fait d'utiliser la récursion au lieu de l'itération introduit une limitation importante : la longueur du texte que l'on peut traiter est limitée par la profondeur de la pile de récursion, elle-même limitée par l'espace mémoire disponible, alors qu'un programme itératif pourrait traiter des textes de longueur illimitée.
Noter que le programme tel qu'il est écrit ne traite pas correctement les caractères accentués. Par exemple, il reconnaît à tort le mot « américain » comme un bivocalisme A-I. On pourrait facilement le modifier pour corriger cela, mais je ne l'ai pas fait pour ne pas en alourdir la lecture.
char diffchar = 'I';Cette variable contiendra 'a' ou 'i' selon la dernière de ces deux lettres qu'on aura rencontrée. C'est la seule information qui soit mémorisée entre deux appels successifs de la fonction principale, et qui doive donc être dans une variable globale.
main() {L'unique fonction qui compose ce programme.
char inchar;Le caractère lu sur l'entrée standard (input character).
int nbcharin, flag;Comme on ne peut pas écrire de
else
, chaque
valeur testée dans un if
doit être
stockée dans une variable pour que sa négation puisse
être testée à nouveau (cf. ci-dessous). La
variable nbcharin
(number of characters on
input) stocke la valeur renvoyée par scanf
,
et flag
le résultat du test décidant si
le dernier caractère lu est acceptable ou non.
if ( (nbcharin = scanf("%c", &inchar)) != 1 )Lit un caractère sur l'entrée standard, le place dans
inchar
, et place dans nbcharin
le nombre de
caractères effectivement lus : 1 en temps normal, 0 en
fin de fichier, -1 en cas d'erreur. Dans ce programme on ne
distingue pas les erreurs de lecture d'une fin de fichier normale.
printf("Satisfaisant.\n");Si on a atteint la fin de fichier sans s'arrêter auparavant, c'est que tous les caractères lus depuis le début convenaient : le texte lu sur l'entrée standard est bien un bivocalisme alterné en A-I.
if ( nbcharin == 1 ) {Le «
else
» du « if
» précédent,
simulé en effectuant le test opposé (nbcharin ==
1
au lieu de nbcharin != 1
).
('A' <= inchar && inchar <= 'Z') ? (inchar += 32) : 0 ;Si le caractère lu est une lettre majuscule (caractères entre A et Z dans le code ASCII), on la convertit en minuscule en lui ajoutant 32 (c'est une caractéristique du code ASCII). Ceci s'écrirait plus classiquement avec un
if
mais on
a utilsé une expression conditionnelle parce que le
i de if ne respecterait pas l'alternance.
if ( ( flag = (inchar > 'd' && inchar < 'f') || (inchar > 'n' && inchar < 'p') || (inchar > 't' && inchar < 'v') || (inchar > 'x' && inchar < 'z') || (inchar == diffchar) ) )Met dans
flag
la valeur 1 si le dernier
caractère lu n'est pas acceptable,
c'est-à-dire si c'est une des voyelles e o u y ou
s'il est identique au dernier a ou i
rencontré. Comme on ne peut pas faire apparaître la
lettre e dans le programme, on vérifie à la
place si le caractère est situé entre d et
f, et de même pour les autres voyelles interdites.
printf("'%c' pas si satisfaisant.\n", inchar);Si le caractère est inacceptable, on affiche un message pour le signaler.
if ( ! flag ) {Le «
else
» du « if
»
précédent, simulé en testant l'opposé de
sa condition. La suite n'est exécutée que si le dernier
caractère lu est satisfaisant.
diffchar = (inchar != 'i') ? (('a' != inchar) ? diffchar : inchar) : 'i';Le sens de cette instruction est : si
inchar
vaut
'a' ou 'i', le copier dans diffchar
,
sinon laisser diffchar
inchangé. Il y a beaucoup
de façons équivalentes d'exprimer cela avec des
if
et/ou des expressions conditionnelles : on en a
choisi une qui respecte l'alternance.
main();On rappelle récursivement l'unique fonction de ce programme. Bien qu'il soit peu courant de voir un appel à la fonction
main
à l'intérieur d'un prgramme
C, c'est une fonction comme les autres et cela ne pose
aucun problème particulier. La récursion
s'arrête (et le programme se termine) dès qu'une
exécution de la fonction n'atteint pas cette instruction,
c'est-à-dire soit en cas d'erreur de lecture ou de fin de
fichier sur l'entrée standard (premier couple de
if
), soit lorsqu'on rencontre un caractère ne
satisfaisant pas la contrainte (deuxième couple de
if
).
} } }C'est fini !
Nicolas Graner, 1999, Licence Art Libre