precedant | suivant | sommaire

Le Langage C


 - 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).
 

Compilation

Une fois écrit, notre programme dois être exécuté.
Pour le moment notre programme n'est qu'un simple fichier texte placé sur le disque. Le microprocesseur ne sais surement pas quoi en faire. Il nous faut traduire ce programme C dans le langage du processeur.
Cette traduction est faite par le compilateur C qui transforme du C en langage machine. On aurai pu appelé le compilateur "traducteur" ; on appelle compilateur de langage un programme qui transforme un langage en un autre, le langage produit étant sémantiquement moins riche que le premier.
En général le langage en entrée du compilateur est appelé langage source, ou source, le langage produit est appelé le code objet (attention, ici, l'objet ne fait pas référence à la programmation objet).
Pour compiler notre programme hello.c, nous tapons sur la ligne de commande:

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 !
-------------------------------------

Fonction, Bloc d'instructions


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.
 

Variables


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.
 

Affectation


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`;
 

Affichage

Nous avons vu que la fonction printf sert à l'affichage à l'écran.
Elle attend comme premier paramètre une chaine de format.
Tous les caractères sont affichés tels quels.
Dans cette chaîne les séquences de caractères %c, %d, %f sont remplacé par la valeur des arguments correspondant, lus respectivement comme un caractère, un entier et un réel.
La séquence de caractère \n est remplacé par un saut à la ligne et la séquence \t est remplacer par une tabulation.
Nous avons:
  printf (" le caractère %c et l'entier %d\n", `a`, 123);

# 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.
 

Exemple


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.
 
 

Deuxième Partie

 

Entrées/Sorties Console

----------------------
Les entrées/sorties en C font appel à des fonctions qui ne sont pas forcement évidantes; ce sont des fonctions acceptant un nombre d'argument variable.
Or elles sont indispensable dés que l'on souhaite écrire des programmes d'exemple.
On va donc essayer de décrire les deux principales fonctions d'entrées/sorties en donnant une expliquation sommaire de leur fonctionnement.On y reviendra aprés plus en détail, quitte à les réécrires.

****************************************************************************

Affichage

--------
La principale fonction qui permet d'afficher des messages à l'écran est printf (il existe d'autre moyens plus primitifs de manipuler l'écran mais printf est le plus immédiat).
C'est une fonction dont le nombre d'argument est variables.
Le premier argument est obligatoirement une chaîne de caractères, appelée chaine de formatage.
Cette chaîne est affichée telle quelle, mise à part un certain nombre de séquences de caractères qui modifient l'affichage:

      \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");
=>  bonjour
  tout le monde!

  printf ("caractère %c, entier %d\n", 'e', 123);
=>  caractère e, entier 123

  printf ("réel %f, chaîne %s\n", 123.0, "hello");
=>  réel 123.0, chaîne 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);
=>  rien

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");
=>  aléatoire 9566758

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");
=>  Segmentation error. Core Dumped

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');
=>  code ascii de 'e' = 64

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.

***************************************************************************

Lecture

La principale fonction de lecture de C est scanf.
C'est aussi une fonction dont le nombre d'argument est variables, dont le
premier est obligatoirement une chaîne de format. Les caractères de cette
chaînes sont des caractères devant être lus.
Dans cette chaîne, les séquences %doivent correspondre à des variables qui
reçoivent les valeurs lues.
C'est variables doivent être préfixées par le caractère & (on verra plus tard
que ce son des adresses).

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);

=> Entrez un entier préfixé par x:
   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.

***************************************************************************

Programme


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

=> Premier entier:
 123 [ENTER]
 Second entier:
 123 [ENTER]
 123 + 123 = 246

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.)

*****************************************************************************

Itération

Il serai bien evidement plus pratique de pouvoir écrire une boucle dans le main afin de permettre une répétition des calculs.

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?

*********************************************************************

do ...while

Une autre forme d'iteration peut être écrite avec la forme do ...while. comme:
do instruction while (condition);
Dans ce cas l'instruction est d'abord executée, puis la condition est évaluée.
Cette forme convient très bien à notre exemple, car on a pas besoin d'initialiser la variable somme:

      void main (void) {
 int a, b, somme;

 do {
    /* même corps */
 } while (somme != 0);
     }

***********************************************************************

for

--
L'instruction for est la forme la plus générale pour écrire des itérations. Sa syntaxe est la suivante:

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;).
 

*******************************************************************************
*******************************************************************************

Le Langage C troisiéme partie

-----------------------------
Dans les deux chapitre précedent on a vu:
- comment écrire notre premier programme en C.
- comment définir la fonction principale d'un programme, point d'entrée de tout les programme C. Pour écrire cette fonction, on a entouré les expressions entre accolades. Les accolades permettent de délimiter un bloc d'instructions. Dans le bloc, les instructions sont séparées par des points virgules ; elles seront executées unes à unes, en séquence.
Nouis savons aussi déclarer des variables, avec les types de base, comme les entier ou les caractères.
Nous avons également vu les instruction d'itération et de test permettant de modifier l'ordre d'execution des instructions.

-----------------------------------------------
 

Portées des variables


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.

*****************************************************************************

Les fonctions

-------------
Un programme commence toujours par la déclaration de la fonction main. Tiens, fonction ? comment fait t'on pour déclarer d'autres fonctions ?
Une fonction est un bloc d'instructions auquel on donne un type pour la valeur de retour,n un nom et des paramêtres:

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

retour



 precedant | suivant | sommaire