- Première partie -
------------------
Extraits des articles trouvé sur Linux-mag de Guilhem
de Wailly http://www.erian-concept.com
Ce document est une simple copie de l'article de Guilhem de Wailly
avec des fautes d'ortographes en plus, et des phrases peut être moins
bien tournées, ce document je l'ai fait pour moi, car je retiens
mieux les définitions que je tape.
Mais si ça peut servir à quelqu'un des exemple ici,
ici,
ici .....
(alain adelmar)
Historique, né dans les labo de Bell AT&R par Dennis Ritchie,
en 1972. C'est un langage algorithmique et impératif appartenant
à la même famille que le Pascal, PL/1. C'est un langage
primitif, à la frontière de l'assembleur et des langages
évolués. Il est facile à compiler, facile à
porter doté d'un compilateur facile et rapide.
Au début, UNIX était écrit en assembleur(langage
machine) et en B, un ancêtre du C, non typé.
On le trouve dans casiment toutes les architéctures, du processeur
spécialisé pour le traitement du signal (DSP), en passant
par les micro-ordinateurs, et jusqu'au super calculateurs.
Le C est portable d'une architecture à l'autre. Toutefois il
peut poser problème, notamment en ce qui concerne la taille des
mots.
Sur un 8086(Amstrad, Atari), un entier est codé sur 16bits alors
que sur un 386 (PC), il est codé sur 32bits.
Le C est utiliser pour programmer des applications aussi diverses que
les systèmes d'exploitations, les gestionnaires de base de données,
les interface graphiques.
Le langage C est normalisé par l'American Natitional Standard
Institut (ANSI) depuis 1982. Avant le langage était dit normalisé
par un annexe du livre de Brian Kernignan et Denis Ritchie, connu sous
le nom de K&R.
Le langage C a continué d'évoluer en se dotant d'un moteur
à Objet pour donner naissance au langage C++, conçu par Bjarn
Stroustrup. Le langage C++ offre de réelles inovations, mais les
compilateurs C++ restent lents, et les puristes préférent
continuer à développer en C.
Au cours des articles, nous montrerons comment programmer en C en utilisant
une approche orientée objets.
Hello World
Nous supposons que nous travaillons sur une station UNIX, de préférence
Linux.
Le compilateur C a été correctement installé,
ainsi que les bibliothéques de dévelloppement.
Nous recommendons de travailler avec l'editeur de texte emacs ou jed,
qui posséde tout deux un mode C qui reconnait la syntaxe C. Emacs
va plus loin puisqu'il permet de compiler automatiquement les programmes,
de le deboger: il propose un véritable environnemùent de
programmation C.
Plaçons-nous dans un répértoire de travail. Tapons
le fichier suivant, nommé hello.c:
#include <stdio.h>
void main (void) {
printf ("hello word !\n");
}
Emacs permet d'indenter correctement les programmes. Pour cela tapez
la touche TAB au debut sur la ligne( ou au debut d'une ligne). Au lieu
de placer une tabulation, emacs indente la ligne (indenter signifie placer
des blancs au debut des lignes pour montrer la hiérarchie des blocs
du programme).
Seul la première pression sur TAB à uneffet, les autres
sont ignorées.
Donc lorsque l'on tape ENTER pour retourner au debut de la ligne, on
tape aussi TAB pour indenter la ligne.
La mise en forme d'emacs est conforme à l'usage. Celaz ne veut
pas dire que le programme ne sera pas reconnu si l'on opte sur une autre
mise en forme.En fait , seules les lignes commencant par # doivent être
suivient d'un saut à la ligne. Les autres peuvent être écrites
comme l'on souhaite.
On aurai pu écrire :
#include <stdio.h>
void main(void){printf("hello word !\n");}
Le programme est constitué de mots séparés par
des séparateurs. Le séparateur le plus important est le blanc.
En C, un blanc est un espace, une tabulation, un saut à la ligne,
ou toute autre combinaison de blancs.
Les autres séparateurs sont :
< > ( ) " { } [ ] ; , et les opérateurs arithmétiques.
Le lecteur ce référera aux ouvrages sités en annexe
pour lire la grammaire compléte du langage C.
On reconnait donc les mots #include, stdio.h,
void,
main
et printf. Comme dans la plupart des autres langages, les chaines
de caractères constante sont entourées de guillemets.
Attention, le C fait aussi la difference entre les majuscules et minuscules.
La première ligne indique au compilateur d'utiliser la bibliothéque
des entrées/sorties standard. Nous verons ce que cela implique exactement.
La seconde ligne définit une fonction appelée main. Cette
fonction ne retourne rien (premier void) et n'a pas de paramètres
(void entre parentéses). La fonction main est un peu particulière:
dans tout programme C, quel que soit la taille, il n'y a qu'une seule fonction
appelée main, et c'est le point d'entrée du programme.
Ce qui est placé entre les accolades constitu le corps de la
fonction : ce sont des instructions séparées par des point-virgules.
Ce corps ne comporte qu'une instruction. C'est un appel à la fonction
printf, avec son argument, la chaîne de caractères constante
Hello word !\n
La fonction printf est chargée de l'affichage à l'écran
des arguments. Notez que le caractère \n dans la chaîne à
une signification spéciale : il sera remplacé par un saut
de ligne lorsqu'il sera affiché (comme vbCrLf dans VBasic).
gcc -o hello
# l'option -o signifie "origine" ce qui veut dire compile le programme
en C qui a comme origine le source hello, et il renvois le programme :
hello.c
gcc est le nom du compilateur C du GNU. L'option -o hello force le compilateur à fabriquer un exécutable dont le nom sera hello. Cette commande fabrique donc un fichier éxécutable hello. C'est un nouveau programme dans notre systeme.
Maintenant tapons :
$ ./hello
hello word !
$
Ca marche, con !!!
récapitulation:
Ca marche, d'abord je tape à l'invite du shell
[coolget@al coolget]$ gcc -o hello hello.c
il me renvoit ces deux lignes, vu que j'avais ajouter "gcc -o hello
hello.c"
hello.c: In function `main':
hello.c:2: warning: return type of `main' is not `int'
puis j'appelle mon programme:
[coolget@al coolget]$ ./hello
hello word !
-------------------------------------
Le programme précédent nous a montré comment
définir une fonction. Allons un peu plus loin.
Une fonction permet de donner un nom et des paramètres à
un bloc de code.
Un bloc de code est délimité par des accolades. Le contenu
du bloc de code est par convention, indenté. Structurellement, un
bloc de code contient deux parties : la partie des déclarations,
et la partie des instructions :
{
déclaration1;
déclaration2;
...
déclarationm;
instruction1;
instruction2;
...
instructionn;
}
Un bloc de code ne peut contenir aucune déclaration ni aucune
instruction. Ainsi le plus petit bloc est {}.
La partie des déclarations déclare ou définit
des variables locales, qui ne sont accessibles qu'aux déclarations
et aux instructions situées sous elles ou faisant partie du même
bloc ou des blocs inclus.
Elle sont executées de gauche à droite et de haut en
bas.
La définition d'une fonction comprend deux partie:
le prototype et le corps. Le prototype contient le type de la valeur
de retour de la fonction, le nom de la fonction, la liste de ces paramètres.
La description des types de base et du nom des fonctions est donné
plus bas, dans la section "variables".
La liste des paramètres est formée des couples type-nom
des paramètres, séparés par des virgulesd, ourien,
ou le mot clef void. (qui signifie "sans type ").
le corps de la fonction est un bloc. Une fonction ne peut pas être
défini dans un bloc. Ainsi, la définition d'une fonction
s'écrit:
type_retour nom_fonction (liste_paramètres)
déclaration1;
déclaration2;
..
déclarationm;
instruction1;
instruction2;
..
instructionn;
}
Si on omet le corps de la fonction, on obtient la déclaration
de la fonction. Ainsi:
int fonction (int n)
déclare la fonction fonction, retournant un entier et attendant
un entier. On vera plus tard l'utilité des déclarations de
fonctions.
En C, les fonctions ne sont pas des objets de première classe,
comme en Scheme.
Cela signifie qu'il ne gére pas tout à fait de la même
manière la déclaration d'une variable
et la déclaration d'une fonction. On verra plus tard ces différences.
Une variable est représentée en C par un nom. Ce nom
peut contenir des chiffres, des lettres et le signe `_` (de l'alphabétique
et du numérique).
exemple:
1_N_o_m_2_V_a_r_i_a_b_l_e_C_o_r_r_e_c_t => correct
unnom-incorrect => incorrect
Le langage C comporte un certain nombre de type de données de
base. Les principeaux sont:
char
: caractère
int
: entier signé
unsigned : entier
non signé
float :
réel court sur 32bits
double
: réel long sur 64bits
ainsi:
char toto;
int titi;
déclare la variable toto comme de type caractère et titi
de type entier. Le type de la variable va détérminer la quantité
de mémoire qui doit être résérvé dans
la machine pour stocker le contenu de la variable et à aider le
compilateur à détécter les erreurs de type.
Le langae C permet au programmeur de construire ses propres types de
données, comme on le verra plus loin.
Pour donner une valeur à une variable, C définit l'opérateur
= qui s'utilise comme suit:
int entier;
entier = 123;
L'expression à droite du signe = est évaluée et
son résultat est placé dans la variable. Attention ce n'est
pas une équation, mais bien une affectation, ainsi il est possible
d'écrire :
int n;
n = 2;
n = n + 1;
A la fin, la variable n vaut 3. L'expression n = n + 1 n'est pas une
équation.
Il est possible de déclarer des variables et de les initialiser
en même temps, comme dans:
int entier = 123;
Les caractères s'écrivent entre quotes. Ainsi, `a` représente
la lettre a, on peut alors écrire :
char un_car = `A`;
# le caractère a et l'entier 123
printf ("%c\t%c\n%d\t%d\n", `z`, `a`, 456, 123);
# z a
# 456 123
Attention, C ne vérifie pas le nombre et la correspondance des
types entre les séquences % et les arguments.
Dans cet exemple nous allons mettre en pratique nos connaissances:
# include <stdio.h>
void main (void) {
int x;
x = 10;
printf ("x vaut %d\n", x);
}
Nous utilisons toujours la bibliothéque d'entrée / sorties
standard.
Puis nous définissons la fonction main, ne retournant rien et
sans paramètres. Son corps déclare l'entier x. La première
instruction du corps affécte la valeur 10 à la variable x.
Puis elle appelle printf, avec une chaîne de format contenant un
%d qui sera remplacé à l'affichage par la valeur de x, c'est
à dire 10.
****************************************************************************
\n est remplacé par un saut de ligne;
\t est remplacé par tabulation;
\b est remplacé
par un retour en arrière; si le curseur est en debut de ligne,
\b n'aura aucun effet; sinon,
le caractère avant le curseur est
effacé si le curseur
est déplacé d'un caractère vers la gauche.
\" est remplacé par un guillemet;
%c est remplacé
par l'argument supplementaire correspondant lu comme un
caractère; attention aucun contrôle
de type n'est effectué sur les
arguments supplémentaires ;
%d : argument entier ;
%f : argument réel ;
%p : argument pointeur (on verra plus tard) ;
%s : argument chaîne de caractère ;
Nous aurons par exemple :
printf ("bonjour\ntous le monde!\n");
=>
printf ("caractère %c, entier %d\n", 'e', 123);
=>
printf ("réel %f, chaîne %s\n", 123.0, "hello");
=>
Le langage C est trés permissif : il n'efféctue que peu
de contrôles.
Par exemple:
printf ("rien\n",1, 2, 3, 4);
=>
Dans cet exemple, des arguments supplémentaire sont donnés
à la fonction, mais
ils sont ignorés car il ne correspondent à aucun %
printf ("aléatoire %d\n");
=>
Là, le format spécifie un argument lu comme un entier,
mais aucune valeur n'est
donné ; la valeur affichées sera alléatoire et
différente à chaque appel.
printf ("aléatoire %s\n");
=>
Ici, la valeur attendue est une chaîne de caractère, qui
est une donnée
complexe. La valeur aléatoire à peu de chance de correspondre
à une chaîne de
caractère en mèmoire.
printf ("code ascii de '%c' = %d\n, 'a', 'a');
=>
Enfin, nous affichons un caractère comme un entier ; la valeur
affichée
correspond au nombre entier qui sert à coder le caratère
en mémoire. Cette
valeur dépend de la machine utilisée et du système
d'exploitation. Avec UNIX,
il y a de grande chance que cela corresponde au code ASCII.
Donc comme on peut le voir, C n'effectue que peut de contrôle,
il se repose sur
le programmeur.
***************************************************************************
On aura par exemple :
int a;
printf ("Entrez un entier:\n");
scanf ("%d", & a);
printf ("L'entier lu est %d\n", a);
=> Entrez un entier
123 [ENTER]
L'entier lu est 123
En plaçant des caractère dans le format, cela donne :
int a;
printf ("Entrez un entier);
printf (" préfixé par x:\n");
scanf ("x%d", & a);
printf ("L'entier lu est %d\n", a);
=>
x123 [ENTER]
L'entier lu est 123
Si l'on n'introduit pas le caractère x en premier, l'entier
ne sera jamais lu ;
en fait la fonction scanf reste bloquée jusqu'a ce quelle rencontre
le
caractère x, suivi d'un entier.
***************************************************************************
Nous allons utilisernos connaissances sur les entrées/sorties
pour écrire un
programme qui calcule la somme de deux nombres.
Plaçons le code dans le fichier somme.c
#include <stdio.h>
void main (void){
int a, b somme;
/* lecture du premier entier */
printf ("Premier entier:\n");
scanf ("%d", & a);
/* lecture du second entier */
printf ("Second entier:\n");
scanf ("%d", & b);
/* Calcul de leur somme */
somme = a + b;
/* affichage */
printf ("%d + %d = %d\n", a, b, somme);
}
Compilons le programme, et executons le :
$ gcc somme.c -o somme
$ ./somme
=>
123 [ENTER]
123 [ENTER]
essayer le avec d'autre entiers, pour voir. (Dans les exemples l'auteur à omit de mettre le signe (*) dans quelques cas de figure pour les commentaires, et ça ne fonctionne pas, alors soit la version que j'ai ne peut admettre cet oubli, soit l'auteur les à omit (ça, est quelques autres espaces entre les données), soit il est possible de le faire mais pas comme ça, en tout état de cause j'en serais plus une fois que j'aurai appris le bon fonctionnement et surtout, la bonne syntaxe des commantaires.)
*****************************************************************************
while
pour cela il éxiste le mot-clef while qui s'utilise
ainsi:
while (condition) instruction
ou condition est une expression. Tant que cette expression retourne
un résultat non nul, l'instruction est executée.
L'instruction est soit une expression simple, soit un bloc d'expressions
entre accolades.
aisi notre fonction principale devient :
#include <stdio.h>
void main (void) {
int a, b, somme = 1;
while (somme != 0) {
/* lecture du premier entier */
printf ("Premier nombre entier:\n");
scanf ("%d", & a);
/* lecture du second entier */
printf ("second nombre entier:\n");
scanf ("%d", & b);
/* calcul de leur somme */
somme = a + b;
/* affichage */
printf ("%d + %d = %d\n", a, b, somme);
}
}
Pour que la boucle s'arrète il faut que la somme des deux
enier soit égale à zéro, (ex -6 ,6). Nous évitons
que cela ce produise en initialisant somme à 1, au demarrage. Nous
remarquerons que les commentaires s'écrivent entre /* et */.
Les expressions de tests sont :
a == b :vrai si a et b sont égaux;
a != b :vrai si a et b sont différents;
a >= b :vrai si a est plus grand ou égale
à b;
a > b :vrai si a est plus grand que b;
a <= b :vrai si a est plus petit ou égal
à b;
a < b :vrai si a est plus petit que b;
a :vrai si a est non nul;
Le langage C ne posséde pas le type booléen : N'importe quel type de valeur peut jouer ce rôle. La valeur nulle est considéré comme fausse, et les autres valeurs sont considérées comme vraies?
*********************************************************************
void main (void) {
int a, b, somme;
do {
/* même corps */
} while (somme != 0);
}
***********************************************************************
for (init; test; post);
expression
- init L'expression init est exécutée au debut de l'itération.
- test puis le test est évalué et si son résultat
est nul, l'itération fini.
sinon, l'expression est évaluée. Avant de recommencer
l'iteration l'exp ression post est évaluée.
- post l'expression post est évaluée à la
fin de l'iteration,avant de renvoyer sur test, qui soit stoppera
soit renvera à post.
Il est possible de placer plusieurs instructions séparées
par des virgules dans init et post. L'expression peut être un bloc
entouré d'accolades. Ces expressions peuvent aussi êtres vides.
Pour afficher la table de multiplication par 7, nous écrirons:
init i, n = 7;
for (i = 0; i <= 10; i = i + 1) {
printf ("%d x %d = %d\n", i, n, i * n);
}
(pour écrire une boucle infini, nous pouvons faire: for (;;)
expression;).
*******************************************************************************
*******************************************************************************
-----------------------------------------------
Principalement, nous pouvons déclarer des variables à
deux endroits, à l'interieur d'un bloc délimité par
des accolades ou à l'exterieur;
/* cette variable est accessible partout
* en dessous de sa déclaration.
*/
int variable_globale;
void main (void) {
/* cette variable n'est accessible
* que dans ce bloc, partout en
/*dessous de sa déclaration.
int variable_locale
...
}
lorsque une variable est déclarées à l'exterieur
de tout bloc, on dit que cette variable est globale. Cela signifie qu'elle
est accessible de n'importe quel endroit du programme se situant sous la
déclaration (nous verrons plus tard que la visibilité de
la variable est encore plus globale, moyennant quelques aménagements).
Lorsqu'une variable est déclarée à l'interieur
d'un bloc, la déclaration ne peut être faite qu'au debut;
on dit alors que la variable est locale à ce bloc.
Cela signifie que la variable est accésible à toute instructions
du bloc se situant sous la déclaration.
Une variable locale disparait lorsque la dernière instruction
du bloc auquel elle appartient est terminée. Au contraire, une variable
globale ne disparait jamais.
Une variable globale peut "cacher" des variables déclarées
précédement.
int x; /* variable globale */
void main (void) {
int i, j, k; /* variable locale */
j = k = x = 0;
for (i = 0; i < 3; i = i + 1) {
int x; /* ce x
cache le x global */
int j; /* ce j
cache le j local */
j = k + i;
x = 3 * j;
k = x;
printf ("i=%d, j=%d, k=%d, x=%d\n", i, j, k, x);
}
printf ("i=%d, j=%d, k=%d, x=%d\n", i, j, k, x);
}
i=0, j=0, k=0, x=0
i=1, j=1, k=3, x=3
i=2, j=5, k=15, x=15
i=3, j=0, k=15, x=0 alors que le mag donne: i=2, j=0, k=0, x=15
le reste ok
Les variables locales ne peuvent être déclarées qu'au debut des blocs, parmi les autres déclarations:
void main (void) {
int x = 3; /* ok */
int j = x + 4;
/* ok */
printf ("ùd\n", x); /* instruction */
int u; /* déclaration erronées */
*car situées aprés une instruction
*/
}
si l'on essai de compiler le programme suivant, gcc nous dit:
$ gcc fichier.c
c.c: In function 'main':
c.c:6:parse error before 'int'
il est possible d'initialiser les variables locales avec des instructions.
Les variables globales ne peuvent être initialisées qu'avec
des constantes.
On a par exemple:
int a = 3 + 3;
char c = 'e';
int r = rand(); /* rand est le générateur de
nombre aléatoire */
Si l'on tente de compiler un programme où on retrouve les instructions
suivantes, la déclaration de r provoque une erreur car elle n'est
pas initialisée par une constante.
Il est possible de déclarer des blocs sans qu'ils soient associés
à une instruction for ou while;
void main (void) { /* bloc de main */
int x; /*déclaération de main */
... /* instructions */
{ /* bloc */
int a, x; /* déclaration du bloc
*/
... /* instructions */
}
... /* instructions */
Dans ce fragment de programme, nous déclarons un bloc imbriqué, qui permet de définir des variables.
*****************************************************************************
type nom (paramètres) {bloc}
ainsi nous écrivons la fonction main:
void main (void) {bloc}
Cette définition indique une fonction de nom main, ne retournant
rien, n'ayant pas de paramètres et donc le corps est un bloc.
Les paramètres se déclarant avec un type suivi d'un nom,
chacune d'es déclarations étant séparées par
une virgule:
int fonction (int a, int b) {bloc}
Cette écriture définit la fonction fonction retournant
un entier et attendant deux entiers comme arguments.
Une fonction, en C, n'est pas une fonction mathématique: c'est
une séquence d'instructions regroupées dans un bloc auquel
on donne un type, un nom et des paramètres.
On peut ne pas définir des fonctions à l'interieur d'un
bloc : toutes les fonctions sont des variables globales (nous verrons cependant
comment réstreindre la portée des fonctions).
Pour activer le bloc d'instructions d'une fonction, il suffit d'écrire
le nom de la fonction, suivi d'arguments, entre parenthéses :
/* fonction ne retournant rien et
* prenant deux entiers.
*/
void f (int a, int b) {
printf ("%d + %d = %d\n", a, b, a+b);
}
void main (void) {
/* appel à la fonction f avec comme
* argument, les entiers 1 et 2.
*/
f (1, 2);
}
A l'exécution, les arguments de la fonction sont évalués
(ordre applicatif) : ici, 1 et 2. Le résultat de l'évaluation
des arguments est placé à un endroit qui dans le corps de
la fonction, permet aux paramètres de prendre la valeur de ces résultats
(nous verrons plus tard que c'est endroit est une pile). Enfin, les instruction
du corps de la fonction sont exécutées.
Lorsque une fonction souhaite terminer prématurément,
elle appelle l'instruction return avec un argument correspondant au type
de valeur de retour de la fonction. La valeur de retour peut être
placée entre parenthése ou non. Lorsque le type de la valeur
de retour est void, return ne prend pas d'argument:
/* fonction retournant un entier et
* attendant un entier
*/
int g (int a) {
/* la valeur de retour de return peut
* ne pas être placée entre parenthéses
*/
return (a + 3) / 2;
}
void f (int a) {
printf ("g (%d) = %d\n", g(a));
}
void main (void) {
f (1, 2);
}
L'instruction return peut être utilisé autant de fois que
nécessaire, de n'importe quel endroit de la fonction, et pas nécessairement
à la dernière position.
Lorsqu'quelle est exécutée, l'instruction return provaoque
la fin de l'exécution de la fonction en cours et met à disposition,
le cas échéant, la valeur de retour.
Le compilateur vérifie que le nombre et le type des arguments
(en fait le type du résultat de l'évaluation des arguments)
correspondent bien au nombre et au type déclarés des paramètres.
Il vérifie aussi que le type et la valeur de retour (en fait, le
type du résultat de l'évaluation de la valeur de retour)
correspondant au type déclaré de la fonction.
Il y a erreur lorsque le nombre des arguments ne correspond pas au
nombres de paramètres. Par contre, il n'y a qu'un avertissement
lorsque le type ne correspond pas. Le langage C est assez permissif.
Prototype des fonctions
Forts de ce que l'on à appris, nous désidons d'utiliser les fonctions pour écrire.
/* fonction retournant un réel double
* et attendant un réel double
*/
double f (double a) {
printf ("a = %d\n", a);
if (a <= 0.01) {
return (0.0);
}
else {
return g (a * 2.0);
}
}
double g (double x) {
printf ("x = %d\n", x);
if (x <= 0.01) return (0.0);
else return f (x / 3.0);
}
void main (void) {
f (10);
}
Il s'agit d'un programme ayant trois fonctions, dont la fonction principale.
Les deux fonctions f et g sont mutuellement récursives, c'est-à
dire qu'elles s'appellent mutuellement.
Les variables de type double sont des nombres réels au format
long, c'est à dire qu'ils sont codé sur 64bits. L'autre type
pour les réels est float, codé sur 32bits.
Si nous compilons ce programme, nous obtenons les messages:
$ gcc x.c
x.c:11: warning: type mismatch with previous external decl
x.c:7: warning: previous external decl of `g'
x.c:11: warning: type mismatch with previous implicit declaration
x.c:7:warning: previous implicit declaration of `g'
x.c:11: warning: `g' was previously implictly declared to return `int'
La présence des avertissements (warning) n'empéche pas
la création du binaire a.out.
Lorsque l'on exécute ce programme, il s'arrête peu aprés,
dû à la convergence des valeurs (le lecteur pourra remplacer
le test <= 0.01 par == 0.0 et constater quelque chose d'étrange
; nous en reparlerons plus tard).
Le problème rencontré par le compilateur se situe dans
la fonction f : on utilise une fonction g, dont le compilateur ne reconait
rien car il ne l'a pas encore rencontrée. Par défaut, le
compilateur suppose que les fonctions qu'il ne connait pasretournent un
entier. Là, le compilateur n'émet aucun avertissement.
Puis, en parcourant le fichier, il recontre la définition de
g, déclarée comme retournant un double. Il y a conflit entre
ce qu'il avait supposé, g retourne un entier, et ce que l'on déclare
retourne un double.
On pourrait définir la fonction g au-dessus de la fonction f:
le problème serai résolu pour g, mais ce poserai pour la
fonction f, inconnue dans g.
Pour résoudre ce problème, il faut déclarer f
au-dessus de g, définir f, puis définir g.
On utilise pour cela le prototype des fonctions :
un prototype de fonction consiste dans le type de la valeur de retour,
le nom de la fonction, le nom de la fonction, ses paramètres, le
tout suivi d'un point-virgule. Le nom de paramètres peut être
omis. On déclare g avec:
/* déclaration des fontions */
(double a); /* format long
*/
double g (double); /* format court
*/
/* définition des fonctions */
double f (double a) {
printf ("a = %d\n", a);
if (a <= 0.01) {
return (0.0);
}
else {
return g (a * 2.0);
}
}
double g (double x) {
printf ("x = %d\n", x);
/* syntaxe du if sans accolades */
if (x <= 0.01) return (0.0);
else return f (x / 3.0);
}
/* programme principal */
void main (void) {
f (10);
}
Cette fois lorsque le compilateur traite ce fichier, il connait f et
g avant de compiler le corps des fonctions. On remarque que le prototype
de la fonction main n'est jamais le programme lui-même.
Nous aurions pu placer le prototype de g à l'interieur de la
fonction f, comme une variable:
double f (double a) {
/* déclaration de g */
double g (double);
printf ("a = %d\n", a);
if (a <= 0.01) {
return (0.0);
}
else {
return (0.0);
}
}
...
Interface
Les prototypes des fonctions sont un peu la carte de visite d'un programme