031 - ch06 - Programmation Modulaire PDF
031 - ch06 - Programmation Modulaire PDF
031 - ch06 - Programmation Modulaire PDF
Programmation modulaire
Un long programme est difficile à appréhender globalement. Il vaut donc mieux le scinder en
petits programmes : un programme principal fait appel à des sous-programmes, qui peuvent
eux-mêmes faire appel à des sous-programmes, et ainsi de suite. C’est le principe du raffinement
successif. De plus certains sous-programmes peuvent servir dans d’autres programmes, c’est le
principe de la modularité. Ces principes sont mis en oeuvre en langage C grâce aux fonctions.
On peut distinguer, en langage C, les fonctions prédéfinies des bibliothèques (telles que
printf() ou scanf()), livrées avec le compilateur et “intégrées” au programme lors de l’édition
des liens, et les fonctions que le programmeur écrit lui-même en tant que partie du texte source.
Nous avons déjà vu comment utiliser les premières. Nous allons donc nous intéresser ici aux
secondes. Nous verrons aussi, d’ailleurs, la façon de concevoir les fonctions prédéfinies.
61
62 CHAPTER 6. PROGRAMMATION MODULAIRE
6.1 Un exemple
Introduction.- Commençons par donner un exemple simple de définition et d’utilisation d’une
fonction.
Considérons à nouveau, pour cela, notre exemple de fonction que l’on veut évaluer en un
certain nombre de points. Une nouvelle amélioration consiste à dégager la définition de la fonction
en utilisant un sous-programme.
/* Fonct_1.c */
#include <stdio.h>
#include <math.h>
double F(double x)
{
return((sin(x) + log(x))/(exp(x) + 2));
}
void main(void)
{
float x, y;
printf("x = ");
scanf("%f",&x);
while (x != 1000)
{
y = F(x);
printf("f(%f) = %f\n", x, y);
printf("x = ");
scanf("%f",&x);
}
}
L’amélioration provient ici plus particulièrement du fait que l’on ne s’occupe pas de la fonction
particulière dans le corps du programme, mais uniquement du fait que l’on veut afficher sa
valeur en un certain nombre de points, ce qui est l’essence du programme. Il suffit de changer le
sous-programme, bien mis en évidence, lorsqu’on veut changer de fonction.
6.2.1 Syntaxe
6.2.1.1 Règles de définition
Nom d’une fonction.- Une fonction a un nom qui est un identificateur (non utilisé pour une
autre entité, comme d’habitude).
6.2. DÉFINITION D’UNE FONCTION 63
Sémantique.- Ceci correspond à la définition d’une fonction (à plusieurs variables) définie sur
une partie de A1 × A2 × ... × An et à valeurs dans B, où A1 est l’ensemble des entités du type
type1, ... , An l’ensemble des entités du type typen et B l’ensemble des entités du type type.
Le type vide.- Il est prévu un type lorsque la fonction ne renvoie pas de valeur, ou lorsqu’elle n’a
pas d’argument, le type void. Nous avons déjà rencontré ce type depuis le début.
Type de la valeur de retour.- La valeur de retour d’une fonction peut être soit d’un type de
base (char, short, int, long, float, double, long double), soit un pointeur (notion que nous
verrons plus tard) vers n’importe quel type (simple ou complexe) de données.
L’instruction de renvoi.- Les valeurs de retour des fonctions sont renvoyées à la fonction appelante
grâce à l’instruction return.
Emplacement des instructions de retour.- La rencontre (au cours de l’exécution, pas de la défi-
nition) d’une instruction return met fin à l’exécution des instructions d’une fonction et rend le
contrôle du programme à la fonction appelante.
Les parenthèses ne sont pas obligatoires. On peut écrire :
64 CHAPTER 6. PROGRAMMATION MODULAIRE
return(expression);
ou :
return expression;
Si le type de expression ne coı̈ncide pas avec le type de la valeur retournée tel qu’il est défini
dans l’en-tête de la fonction, alors le compilateur le convertit comme il faut.
La spécification de expression est optionnelle. Si elle est omise alors la valeur retournée est
indéfinie.
6.2.1.4 Paramètres
Notion.- Les arguments d’une fonction sont appelés ses paramètres en langage C. Le nom de
la fonction est suivi de la liste des paramètres et de leurs types.
Paramètres formels et paramètres effectifs.- Il faut bien faire la différence entre les paramètres
au moment de la définition d’une fonction et les paramètres qui seront donnés lors de l’appel
d’une fonction.
Les paramètres spécifiés lors de la définition d’une fonction sont qualifiés de paramètres
formels. Les paramètres transmis à la fonction lors de son appel de la fonction sont appelés les
paramètres effectifs.
Un paramètre formel est une variable locale, connue seulement à l’intérieur de la fonction
pour laquelle est définie ce paramètre.
Les paramètres formels et effectifs doivent correspondre en nombre et en type ; par contre les
noms pouvent différer.
double F(double x)
{
double y;
if (x == 0) y = 1;
else y = sin(x)/x;
return(y);
}
6.2. DÉFINITION D’UNE FONCTION 65
ou
double F(double x)
{
if (x == 0) return(1);
else return(sin(x)/x));
}
Ceci nous donne un exemple avec deux utilisations de return, à cause de la définition par cas.
Cet exemple est intéressant à plusieurs titres : la fonction a deux paramètres et, de plus, de
types différents ; le corps comporte des déclarations de variables, dites variables locales ; on
utilise une boucle pour définir la fonction. On remarquera que nous sommes obligés d’introduire
la variable locale y car PUISS ne pourrait pas être utilisé à droite d’une affectation.
/* intro.c */
#include <stdio.h>
void WELCOME(void)
{
printf("Welcome to this fantastic program");
printf("\nwhich proves to you the power");
printf("\nof the modularity. \n");
}
void BIENVENUE(void)
{
printf("Bienvenue dans ce merveilleux");
printf("\nprogramme qui vous montre la");
printf("\npuissance de la modularite. \n");
}
void main(void)
{
char L;
66 CHAPTER 6. PROGRAMMATION MODULAIRE
Cet exemple est intéressant pour deux raisons : la fonction a deux paramètres de même type et
l’algorithme est non trivial.
6.2.3.1 Rappel
Principe.- Soit f une fonction réelle de la variable réelle, définie sur un segment [a, b], continue,
strictement monotone et telle que f (a) et f (b) soient de signes opposés. Alors il existe une et
une seule valeur c de [a, b] telle que f (c) = 0.
Même si on sait calculer f (x) pour toute valeur x de [a, b], il n’est pas évident de trouver
une valeur exacte de c. Par contre pour tout ǫ > 0 on peut trouver une valeur c̄ telle que :
|c̄ − c| < ǫ, c’est-à-dire une valeur approchée à ǫ-près.
On divise l’intervalle en deux parties égales en posant d = a+b2 : si f (d) est de même signe que
f (a) alors c appartient à l’intervalle [d, b], sinon il appartient à l’intervalle [a, d]. On peut donc
restreindre l’intervalle de recherche en posant, par exemple dans le premier cas, a = d et b = b.
Le nouvel intervalle est d’amplitude moitié. On recommence ainsi jusqu’à ce que l’amplitude soit
inférieure à ǫ et on peut alors prendre pour c̄ n’importe quelle valeur du dernier intervalle [a, b],
par exemple a.
6.2. DÉFINITION D’UNE FONCTION 67
/* dicho_1.c */
#include <stdio.h>
#include <math.h>
void main(void)
{
float a, b, d, e, ya, yb, yd;
printf("Dichotomie \n");
printf("a = ");
scanf("%f", &a);
printf("b = ");
scanf("%f", &b);
printf("erreur < ");
scanf("%e", &e);
ya = a*a*a*a*a - a + 1;
yb = b*b*b*b*b - b + 1;
while (b - a >= e)
{
d = (a + b)/2;
yd = d*d*d*d*d - d + 1;
if (ya*yd < 0)
{
b = d;
yb = yd;
}
else
{
a = d;
ya = yd;
}
}
printf("c = %e. \n", d);
}
/* dicho_2.c */
#include <stdio.h>
#include <math.h>
float f(float x)
{
return(x*x*x*x*x - x + 1);
}
void main(void)
{
float a, b, d, e;
printf("Dichotomie \n");
printf("a = ");
scanf("%f", &a);
printf("b = ");
scanf("%f", &b);
printf("erreur < ");
scanf("%e", &e);
while (b - a >= e)
{
d = (a + b)/2;
if ( f(a)*f(d) < 0 ) b = d;
else a = d;
}
printf("c = %e\n", d);
}
Exercice.- En fait la présentation du résultat n’est pas satisfaisante : on préférerait six chiffres
après la virgule correspondant aux six premiers chiffres du développement décimal de c.
Concevoir un programme qui demande, non pas l’erreur, mais le nombre de chiffres après la
virgule que l’on désire et qui donne le résultat expliqué dans la phrase précédente.
Ne pas oublier que le tronqué d’une valeur approchée ne convient pas toujours : par exemple
1, 979 est une valeur approchée à 10−2 de 1, 986 mais le résultat escompté est 1, 98 et non 1, 97.
6.3. MODE DE TRANSMISSION DES PARAMÈTRES 69
Passage par valeur et passage par référence.- Lorsqu’on ne veut pas changer la valeur d’un pa-
ramètre on parle de transmission par valeur (en anglais call by value), c’est-à-dire que le
sous-programme ne reçoit qu’une copie de la valeur. Lorsqu’on veut se permettre la possibilité
de changer le valeur on parle de transmission par référence (en anglais call by reference).
Mise en place.- Beaucoup de langages permettent ces deux types de transmission. C’est le cas,
par exemple, du langage PASCAL. Le langage C, quant à lui, ne connaı̂t que la transmission
par valeur, la transmission par référence est émulée en envoyant la valeur de l’adresse grâce aux
pointeurs (comme nous le verrons ci-dessous).
/* valeur_1.c */
#include <stdio.h>
void affiche(int n)
{
printf("%d \n", n);
}
void main(void)
{
int n = 3;
affiche(n);
}
#include <stdio.h>
void affiche(int n)
{
n = 4;
printf("%d \n", n);
}
void main(void)
{
int n = 3;
affiche(n);
printf("%d \n", n);
}
Son exécution fait afficher 4 puis 3. La fonction affiche() n’a donc pas changé la valeur de n
dans la fonction principale.
#include <stdio.h>
int n = 3;
void affiche(void)
{
n = 4;
printf("%d \n", n);
}
void main(void)
{
affiche();
printf("%d \n", n);
}
L’exécution de ce programme fait afficher 4 puis 4. La fonction affiche() a donc changé la
valeur de n dans la fonction principale.
6.3. MODE DE TRANSMISSION DES PARAMÈTRES 71
Remarque.- L’utilisation des variables globales dans un programme est permise, mais il vaut
mieux éviter de s’en servir. Cela va en effet à l’encontre de la clarté de la modularité. Lorsqu’on
considère un sous-programme on devrait voir d’un seul coup d’oeil toutes les variables qui y
interviennent. C’est tout l’intérêt des arguments.
Exemple 2.- Remarquons que, dans le cas de la variable globale ci-dessus, la fonction affiche()
n’avait pas d’argument. Considérons, par opposition, le programme suivant :
/* global_2.c */
#include <stdio.h>
int n = 3;
void affiche(int n)
{
n = 4;
printf("%d \n", n);
}
void main(void)
{
affiche(n);
printf("%d \n", n);
}
Son exécution fait afficher 4 puis 3. La déclaration de l’argument n dans la fonction affiche() le
fait considérer comme une variable locale. La variable n du corps de cette fonction correspond
à la dernière variable déclarée, donc à la variable locale, et non à la variable globale ; ceci explique
pourquoi la valeur de la variable globale, elle, n’a pas changée.
Remarque.- Le nom de variable locale se justifie de par la position de sa déclaration. Elle se justifie
aussi car une telle variable n’est pas connue du programme dans son entier mais uniquement de
la fonction (plus généralement du bloc) dans laquelle elle a été déclarée.
Ceci montre l’intérêt d’utiliser le plus possible des variables locales : c’est une protection
supplémentaire contre les erreurs de programmation.
Nom d’une variable locale.- Nous avons dit précédemment (avant de considérer les fonctions)
qu’on ne pouvait pas déclarer le même identificateur comme variable à deux endroits différents.
Il faut assouplir un petit peu cette règle avec l’utilisation des sous-programmes : en effet, pour
un (gros) programme, des sous-programmes différents peuvent être conçus par des personnes
différentes, qui peuvent donc penser au même nom de variable ; ceci sera détecté lors de la
compilation, certes, mais cela risque de ne pas faciliter les choses. On permet donc en général,
72 CHAPTER 6. PROGRAMMATION MODULAIRE
Priorité des variables locales sur les variables globales.- Un problème se pose alors :
Quelle est la variable considérée lors de son utilisation ?
La réponse est simple : celle qui a été déclarée le plus récemment, c’est-à-dire la plus locale à
l’intérieur du sous-programme (et, plus généralement, à l’intérieur du bloc).
DÉFINITION.- Une déclaration de fonction fournit des indications sur le nom de la fonction,
sur le type de la valeur retournée et sur le type des paramètres.
Il faut, avec cette nouvelle notion, revenir sur la règle disant qu’une fonction doit être définie
avant d’être utilisée. La règle est en fait la suivante.
RÈGLE 1.- Une fonction doit être déclarée ou définie avant d’être utilisée. Elle doit être déclarée
dans un module dans lequel elle est utilisée si elle est définie dans un autre.
Nous reviendrons plus tard sur la deuxième partie de la règle. Indiquons aussi une autre règle.
RÈGLE 2.- Une fonction peut être déclarée plusieurs fois, mais elle ne peut être définie qu’une
seule fois.
Prototype complet.- La syntaxe de la déclaration d’une fonction sous forme de prototype com-
plet est la suivante :
type nom(type1 variable1, ... , typen variablen);
où type, type1, ... ,typen sont des types et où nom et variable1, ... , variablen sont
des identificateurs (le nom de la fonction et les noms des paramètres).
Prototype simplifié.- La syntaxe de la déclaration d’une fonction sous forme de prototype sim-
plifié est la suivante :
type nom(type1, ... , typen);
Autrement dit, on renonce aux noms des paramètres, puisqu’ils ne sont pas utiles.
6.4. DÉCLARATION DES FONCTIONS 73
void main(void)
{
double f(double x);
float x, y;
printf("x = ");
scanf("%f",&x);
while (x != 1000)
{
y = f(x);
printf("f(%f) = %f\n", x, y);
printf("x = ");
scanf("%f",&x);
}
}
double f(double x)
{
return((sin(x) + log(x))/(exp(x) + 2));
}
Exemple de déclaration globale.- Le programme suivant illustre la notion de déclaration globale
de fonction :
/* dec_glo.c */
#include <stdio.h>
#include <math.h>
void main(void)
{
float x, y;
printf("x = ");
scanf("%f",&x);
while (x != 1000)
{
y = f(x);
printf("f(%f) = %f\n", x, y);
printf("x = ");
74 CHAPTER 6. PROGRAMMATION MODULAIRE
scanf("%f",&x);
}
}
double f(double x)
{
return((sin(x) + log(x))/(exp(x) + 2));
}
6.4.3 Pragmatique
Pour mettre un peu d’ordre dans un programme faisant intervenir beaucoup de fonctions, on peut
commencer par la définition de la fonction principale, précédée de la déclaration des fonctions
dont elle a besoin. On définit ensuite ces fonctions auxiliaires, chacune d’elles précédée des
déclarations des fonctions auxiliaires de second niveau dont elles ont besoin, et ainsi de suite.
/* calcul.c */
#include <stdio.h>
#include <math.h>
#include "defini.h"
void main(void)
{
float x, y;
printf("x = ");
scanf("%f",&x);
while (x != 1000)
{
y = f(x);
printf("f(%f) = %f\n", x, y);
printf("x = ");
scanf("%f",&x);
}
}
et :
/* defini.h */
double f(double x)
{
6.6. LES POINTEURS ET LE PASSAGE PAR ADRESSE 75
Commentaires.- 1o ) Le fichier en-tête definiti.h contient ici non pas la déclaration d’une fonc-
tion mais sa définition. On loge en principe le code des fonctions directement dans le programme
ou dans des bibliothèques. Les définitions des fonctions livrées avec le compilateur se trouvent
(sous forme de code objet) dans de telles bibliothèques ayant l’extension .lib (pour LIBrary).
2o ) Le fichier en-tête est écrit entre guillemets verticaux ′′ et non entre les
symboles < et > car il ne se trouve pas dans le répertoire standard des en-têtes mais dans le
répertoire contenant le programme source.
On peut aussi indiquer le chemin complet, par exemple en MS-DOS :
#include "c:\tcwin\programmes\definiti.h"
Types pointeurs.- À tout type correspond un type pointeur. L’intérêt de ceci est dû au fait que,
si le contenu d’une variable pointeur, lui, est de taille constante, la taille de l’entité pointée, elle,
dépend du type de cette entité ; lorsqu’on réserve l’emplacement pour l’entité pointée il faut
donc en connaı̂tre la taille, indiquée par le type du type pointeur.
76 CHAPTER 6. PROGRAMMATION MODULAIRE
Déclaration d’une variable de type pointeur.- Une variable de type pointeur, ou plus concisément
un pointeur, se déclare comme n’importe quelle variable.
Le problème de l’initialisation d’un pointeur.- Une variable de type simple s’initialise directe-
ment, soit par lecture, soit par affectation.
Il est dangereux d’attribuer directement une valeur à un pointeur car celle-ci est une adresse
de la mémoire centrale, qui peut donc avoir été choisie par le compilateur pour une autre variable
ou, pire, pour une partie du programme. D’ailleurs, dans beaucoup de langages (tel que le langage
Pascal), l’adresse n’est pas accessible directement.
Accès au contenu pointé.- La référence à une variable pointeur donnera l’adresse de l’entité
voulue et non l’entité elle-même. Il faut donc un moyen pour désigner cette entité. La façon de
faire dépend du langage considéré.
Déclaration d’une variable d’un type pointeur.- La syntaxe de déclaration d’une variable de nom
ptr (pour PoinTeR ou PoinTeuR) de type pointeur pointant vers une entité de type type est :
type *ptr;
en utilisant l’astérisque, qui est l’opérateur de pointeur.
On dit aussi que la variable ptr est de type type*.
Remarque.- Le type pointeur est type* mais l’astérisque se place traditionnellement devant la
variable et non derrière le type, ce qui permet des déclarations du genre :
int *ptr, i ;
Attention ! On déclare dans ce cas en même temps une variable i de type entier et une variable
ptr de type pointeur vers un entier et non deux variables pointeur.
Initialisation des pointeurs.- On peut initialiser une variable de type pointeur de plusieurs façons :
- 1o ) affectation directe, par exemple :
ptr = 24680;
évidemment à éviter, car on ne sait pas ce qui a été placé à cette adresse par le compilateur (sauf
pour l’adresse fixe de certains périphériques).
- 2o ) affectation grâce à l’opérateur d’adressage, par exemple :
int i;
int *ptr;
ptr = &i;
- 3o ) affectation d’un autre pointeur, par exemple :
ptr1 = ptr2;
Accès au contenu pointé.- Pour accéder au contenu désigné par le pointeur ptr on utilise encore
l’astérisque comme opérateur d’indirection (ou opérateur de contenu ou opérateur de
référence). Le contenu désigné par ptr a pour nom (composé) *ptr.
6.6. LES POINTEURS ET LE PASSAGE PAR ADRESSE 77
On a par exemple :
int i;
int *ptr;
i = 3;
ptr = &i;
i = *ptr;
*ptr = 34;
/* valeur_2.c */
#include <stdio.h>
void affiche(int n)
{
n = 4;
printf("%d \n", n);
}
void main(void)
{
int n = 3;
affiche(n);
printf("%d \n", n);
}
avec passage des paramètres par valeur fait afficher 4 puis 3. Considérons le programme modifié
suivant :
/* adresse.c */
#include <stdio.h>
void main(void)
{
int n = 3;
affiche(&n);
printf("%d \n", n);
}
Son exécution fait afficher 4 puis 4. La fonction affiche() a donc changé la valeur de n dans la
fonction principale.
Problème de la fonction de saisie.- On pourrait utiliser l’instruction (en fait la fonction) scanf(),
mais à ce moment-là on va à la ligne, ce qui n’est pas très beau. On va donc devoir écrire nous-
même une fonction de saisie d’un entier sans aller à la ligne.
Pour cela nous allons commencer par voir quelques compléments sur le traitement des carac-
tères en langage C.
Constantes caractères.- Une constante caractère doit être entourée d’apostrophes verticales, à
ne pas confondre avec les guillemets pour les constantes textes. Ce qui suit est un exemple de
portion de programme correct :
char c;
c = ’a’;
Constantes spéciales.- Certaines constantes caractères n’apparaissent pas sur le clavier : on dit
qu’elles sont non affichables. On peut cependant les désigner par leur numéro de code ASCII
(lorsque le système d’exploitation utilise ce code, évidemment) précédé d’une contre-oblique, par
exemple :
char c, d;
c = ’\0’;
d = ’\7’;
si on veut initialiser au caractère nul et au bip respectivement.
Pour ce dernier on aurait aussi pu faire :
d = ’\a’;
puisque nous avons déjà vu que cela correspondait à l’alarme. Cette dernière façon de faire
est préférable puisqu’elle est indépendante du système d’exploitation, plus exactement du code
utilisé par celui-ci. On pourra tester tout ceci grâce à un programme char 1.c.
Saisie et écriture des caractères.- On peut saisir et écrire un caractère seul grâce aux fonctions
prédéfinies getchar() et putchar(). La première fonction est sans argument et renvoie un
entier, le code du caractère en question ; elle est bloquante et attend un caractère suivi d’un
retour chariot. La seconde a un argument entier et affiche le caractère dont le code est cet entier.
80 CHAPTER 6. PROGRAMMATION MODULAIRE
/* char_2.c */
#include <stdio.h>
void main(void)
{
char c;
printf("Entrez un caractere : ");
c = getchar();
putchar(c);
putchar(c);
putchar(’\n’);
}
qui permet d’entrer un caractère et de l’afficher deux fois (de façon accolée) sur la ligne suivante.
Inconvénient.- L’inconvénient de la fonction getchar() est qu’il faut terminer par un retour
chariot, ce qui est déjà gênant en soi (il faut appuyer sur deux touches) mais de plus ce passage
à la ligne est pris en compte en écho à l’écran.
Ce problème ne semble pas avoir été pris en compte dans la définition du langage C. Il l’est
dans le cas des compilateurs pour MS-DOS (et Windows) en ajoutant des fonctionnalités.
Exemple d’utilisation.- Le programme suivant demande deux caractères qu’il affiche dans l’ordre
inverse, et tout cela sur une seule ligne :
/* char_3.c
* non portable
* uniquement pour MS-DOS */
#include <stdio.h>
#include <conio.h> /* non portable */
void main(void)
{
char c, d;
printf("Entrez deux caracteres : ");
c = getche(); /* non portable */
d = getche(); /* non portable */
putchar(d);
6.7. PHILOSOPHIE DU RAFFINEMENT SUCCESSIF 81
putchar(c);
putchar(’\n’);
putchar(’\n’);
}
Remarque importante.- Les fonctions getch() et getche() saisissent le passage à la ligne (’\n’)
comme un retour chariot (’\r’).
Principe.- L’idée est simple : on lit l’entier caractère par caractère (on dit au vol), les caractères
étant des chiffres décimaux, en terminant la lecture par un retour chariot (à ne pas répercuter à
l’écran).
Quel est le rapport entre ce qui est lu et l’entier voulu ? Considérons, par exemple, l’entier :
123.
Cela signifie qu’on lit les caractères ‘1’, ‘2’ et ‘3’ et que l’on veut l’entier :
n = 1 × 100 + 2 × 10 + 3.
Comment savoir, lorsqu’on lit le premier caractère, qu’il faut le multiplier, dans notre exemple,
par 100 ?
On n’en a évidemment aucune idée. Heureusement la méthode de Hörner permet de ne
pas s’en préoccuper. Il suffit d’écrire :
n = (1 × 10 + 2) × 10 + 3,
n = 0,
n = n × 10 + 1,
n = n × 10 + 2,
n = n × 10 + 3.
Une fonction.- Ceci nous permet d’écrire la fonction lire entier() que nous incorporons, comme
d’habitude, dans un programme complet, pour la tester.
/* entier.c */
#include <stdio.h>
#include <conio.h> /* non portable */
while (c != ’\r’)
{
if (c == ’0’) *n = (*n)*10;
if (c == ’1’) *n = (*n)*10 + 1;
if (c == ’2’) *n = (*n)*10 + 2;
if (c == ’3’) *n = (*n)*10 + 3;
if (c == ’4’) *n = (*n)*10 + 4;
if (c == ’5’) *n = (*n)*10 + 5;
if (c == ’6’) *n = (*n)*10 + 6;
if (c == ’7’) *n = (*n)*10 + 7;
if (c == ’8’) *n = (*n)*10 + 8;
if (c == ’9’) *n = (*n)*10 + 9;
putchar(c);
c = getch();
}
} /* fin de lire_entier */
void main(void)
{
int n;
clrscr(); /* non portable */
printf("Entrer un entier : ");
lire_entier(&n);
printf(".\n");
printf("Cet entier est : %d. \n", N);
}
Remarque.- Nous avons également utilisé la fonction clrscr() de conio.h permettant d’effacer
l’écran (évidemment pour CLeaR SCReen).
/* relatif.c */
#include <stdio.h>
#include <conio.h> /* non portable */
{
sign = c;
putchar(c);
c = getch();
}
else sign = ’+’;
while (c != ’\r’)
{
if (c == ’0’) *n = (*n)*10;
if (c == ’1’) *n = (*n)*10 + 1;
if (c == ’2’) *n = (*n)*10 + 2;
if (c == ’3’) *n = (*n)*10 + 3;
if (c == ’4’) *n = (*n)*10 + 4;
if (c == ’5’) *n = (*n)*10 + 5;
if (c == ’6’) *n = (*n)*10 + 6;
if (c == ’7’) *n = (*n)*10 + 7;
if (c == ’8’) *n = (*n)*10 + 8;
if (c == ’9’) *n = (*n)*10 + 9;
putchar(c);
c = getch();
}
if (sign == ’-’) *n = - (*n);
} /* fin de lire_relatif */
void main(void)
{
int n;
clrscr(); /* non portable */
printf("Entrer un entier relatif : ");
lire_relatif(&n);
printf(".\n");
printf("Cet entier relatif est : %d.\n",N);
}
/* rationnel.c */
#include <stdio.h>
#include <conio.h> /* non portable */
lire_relatif(b);
} /* fin de lire_rationnel */
void main(void)
{
int a, b;
printf("Entrez un rationnel, d\’abord le \n ");
printf("numerateur puis le denominateur : ");
lire_rationnel(&a, &b);
printf(".\n");
printf("Ce rationnel est %d/%d.\n", a, b);
}
Un programme.- Ceci nous conduit à la fonction simplifie() suivante que l’on peut insérer
dans un petit programme qui demande un rationnel et affiche sa forme canonique :
/* affiche.c */
#include <stdio.h>
#include <conio.h> /* non portable */
void main(void)
{
int a, b;
6.7. PHILOSOPHIE DU RAFFINEMENT SUCCESSIF 85
dans lequel nous vous laissons le soin de reporter le code pour pgcd(), lire relatif() et
lire rationnel().
/* addition.c */
#include <stdio.h>
#include <conio.h> /* non portable */
void main(void)
{
int a,b,c,d,e,f;
lire_rationnel(&a, &b);
printf(" + ");
lire_rationnel(&c, &d);
add_rat(a,b,c,d,&e,&f);
printf(" = %d/%d.\n", e, f);
}
#include <stdio.h>
#include <conio.h> /* non portable */
void main(void)
{
int a,b,c,d,e,f;
char op;
lire_rationnel(&a, &b);
while ((a != 0) || (b != 0))
{
printf(" ");
op = getche();
6.7. PHILOSOPHIE DU RAFFINEMENT SUCCESSIF 87
printf(" ");
lire_rationnel(&c, &d);
if (op == ’+’) add_rat(a,b,c,d,&e,&f);
if (op == ’-’) sub_rat(a,b,c,d,&e,&f);
if (op == ’x’) mult_rat(a,b,c,d,&e,&f);
if (op == ’:’) div_rat(a,b,c,d,&e,&f);
printf(" = %d/%d,\n",e,f);
lire_rationnel(&a,&b);
}
printf(" bye\n");
}
Implémentation de getch().- Il suffit d’implémenter getch() puisque getche() n’est rien d’autre
que la fonction getch() suivie d’un putchar().
/* unix.c */
#include <stdio.h>
#include <termios.h> /* non portable, Unix */
int getch(void)
{
struct termios initial, new;
char c;
tcgetattr(fileno(stdin), &initial);
new = initial;
new.c_lflag &= ~ICANON;
new.c_lflag &= ~ECHO;
tcsetattr(fileno(stdin), TCSAFLUSH, &new);
c = getchar();
tcsetattr(fileno(stdin), TCSANOW, &initial);
return (unsigned char) c;
}
void main(void)
{
char c, d;
printf("Entrez deux caracteres : ");
c = getch();
d = getch();
putchar(d);
putchar(c);
88 CHAPTER 6. PROGRAMMATION MODULAIRE
putchar(’\n’);
putchar(’\n’);
}
Commentaires.- Nous n’allons pas entrer dans tous les détails. Ceci sera éventuellement revu
dans le cours de programmation système.
- 1o ) On fait appel à une bibliothèque (termios.h) de programmation système concernant
les terminaux, conforme au standard POSIX.
- 2o ) On utilise des structures (à savoir initial et new) de type termios. La notion de
structure est un outil de programmation générale que nous verrons plus tard.
- 3o ) Le fichier stdin est le périphérique d’entrée standard, notre clavier. Il porte un numéro
que l’on récupère grâce à la fonction fileno.
- 4o ) La fonction tcgetattr (pour Terminal Configuration GET ATTRibutes) permet de
récupérer la configuration actuelle du clavier et de la sauvegarder dans la structure initial.
- 5o ) On commence alors à reconfigurer le clavier en commençant par recopier la configuration
initiale et on ne va changer que les deux points qui nous intéressent : la première ligne indique
que nous ne voulons pas être dans le mode d’entrée canonique ; la seconde ligne indique que nous
ne voulons pas d’écho (à l’écran).
- 6o ) Nous attribuons alors cette configuration au clavier grâce à la fonction tcsetattr.
- 7o ) Il ne faut pas oublier de reconfigurer le clavier tel qu’il était auparavant lorsqu’on sort
du programme.
Les nombres harmoniques.- Pour un entier naturel non nul n, on appelle n-ième nombre har-
monique, et on note Hn , la somme des inverses des entiers de 1 à n :
1 1 1
Hn = 1 + + + ... + .
2 3 n
Un programme.- Nous allons concevoir un programme qui calcule Hn .
/* Harmonic.c
* Ce programme calcule le n-ieme
* nombre harmonique */
#include <stdio.h>
#include <conio.h> /* non portable */
void main(void)
{
6.8. RÉUTILISATION : CALCUL DES NOMBRES HARMONIQUES 89
int n, a, b, i;
clrscr(); /* non portable */
printf("H(");
lire_entier(&n);
a = 1;
b = 1;
for (i = 2; i <= n; i++) add_rat(a,b,1,i,&a,&b);
printf(") = %d/%d.\n", a, b);
}
Exercices
Exercice 1.- Dans le programme entier.c, on a dix tests pour déterminer le chiffre. Dans la
plupart des codes (c’est le cas du code ASCII), les chiffres ont des codes succesifs. On utilise
donc l’astuce suivante :
chiffre = C - ’0’;
qui donne la valeur du chiffre.
Écrire un programme C effectuant la même chose que le programme entier.c en utilisant
l’astuce précédente.
90 CHAPTER 6. PROGRAMMATION MODULAIRE