Gengis Khan
 ANTIROUILLE
 
Initiation à la programmation

Contact :    gengis.scann@free.fr

o Contenu de cette page :


1
oPréface: les langages

2
oAspect pratique
3
oUn premier programme
4
oLes déclarations

5
oLes instructions de base

6
oLe pré-processeur

7
oAutres déclarations

8
oLes fichiers textes

9
oLes fonctions et procédures

10
oPortée des variables

11
oLes pointeurs et l'allocation programmée

12
oAspect numérique

13
oOrganisation des fonctions dans un logiciel
14
oBibliographie
15
o Notes



oPréface : les langages de programmation

o Définition

Programmer : établir à l'avance une suite d'opérations, les phases d'un projet. En informatique, fournir à un ordinateur les données et les instructions concernant un problème à résoudre ou une tâche à exécuter (Petit Larousse).

Donner des instructions à un ordinateur suppose la connaissance d'un  langage de programmation. C'est un langage qui  peut être traduit en un code exécutable, c'est à dire un fichier compréhensible par votre ordinateur. Par exemple ce que WINDOWS nomme application, un fichier dont le nom a la forme toto.exe. Le programme qui permet de tranformer un (des) fichier(s) source(s) en un code exécutable s'appelle un compilateur, ou un interpréteur dans certains cas.

oLes langages

Les langages de programmation dits "évolués" existent depuis un demi-siècle. Les deux ancêtres FORTRAN (pour les scientifiques) et COBOL (pour la gestion) ont subi quelques replâtrages, mais sont toujours là.

Après quelques années de développements anarchiques de logiciels (ils le sont toujours un peu d'ailleurs), il devint nécessaire d'écrire des programmes plus maîtrisables et plus  fiables. En 1971 la NASA perdit une sonde spatiale dans l'Atlantique  (MARINER 8)  car le compilateur FORTRAN  avait laissé passer un bug assez pitoyable.  Le gouvernement américain investit alors dans ce qui aurait dû devenir un langage majeur : Ada La programmation devint structurée, modulaire, abstraite, et actuellement "objet". Des  langages et des dialectes spécialisés sont apparus, certains ont disparu, d'autres ont survécu.

Citons rapidement d'autres survivants :  PASCAL fut un langage structuré très apprécié pour ses vertus pédagogiques. Ce fut le langage C qui connut un succès universel un peu surprenant : il n'avait été conçu au départ par Dennis RITCHIE non pas comme un langage de programmation général, mais pour programmer le système UNIX sur diverses machines.  Au début des années 80, Bjarne STROUSTRUP invente C++ , un langage objet, qui lui aussi connait un gros succès. Puis apparait JAVA, un langage interprété plus spécialisé dans les interfaces graphiques, dans les bouts de programmes tournant sur le net.

D'une certaine façon, ils se ressemblent tous.  Tous ont des défauts et des qualités. On choisit un langage en fonction du travail que l'on doit faire, puis en fonction de la maîtrise qu'en a le programmeur.

Contrairement à une idée reçue, la qualité de la programmation dépend plus de la compétence du programmeur/concepteur que du choix du langage. C'est le programmeur qui choisit de découper un logiciel en composants indépendants et maîtrisables, qui choisit les bons algorithmes, qui écrit ses programmes de façon lisible ou non. La "puissance du langage" vous permettra de gagner du temps de développement, en généralisant plus facilement certains composants. Mais c'est comme pour la musique. Si vous donnez un piano à queue à un ringard, il jouera toujours aussi mal.

oPour ce cours, on a choisi un sous-ensemble de C++

On a choisi comme langage support de ce cours le C++ pour les raisons suivantes :
Le but de ce cours est de vous apporter quelques principes généraux pour vous permettre d'apprendre le plus facilement possible n'importe quel nouveau langage et de vous adapter à tout environnement de programmation : petit programme à la maison, logiciel professionnel développé seul ou en équipe.

Retour au début


oAspect pratique  

Les exemples qui figurent dans ces pages (fichiers XXX.cpp, ".cpp" étant une des extensions autorisées pour le C++) peuvent être exécutés par l'intermédiaire d'un compilateur C++. Ils ont été testés au moyen de  g++, le compilateur c++ de GNU diffusé gratuitement.  g++ est utilisable sur Linux.

Si vous êtes sous WINDOWS, vous pouvez  utiliser l'émulateur d'UNIX CygWin ( http://www.cygwin.com) : CygWin crée une fenêtre alphanumérique sur votre ordinateur, et vous pouvez travailler comme si vous étiez sur UNIX.  On peut aussi utiliser Turbo C ou Visual C++, produit payant de Microsoft. Le choix et l'utilisation du compilateur ne font pas partie des objectifs de ce cours. Même si nous avons une idée précise de ce que sont les produits Microsoft.

Tous les exemples du cours peuvent être téléchargés.  En appuyant sur la touche "shift" de votre clavier,  cliquez  avec le bouton gauche votre souris le nom du fichier incriminé (par exemple hello.cpp )  . Votre navigateur vous propose alors de copier le fichier quelque part sur votre ordinateur. Si vous cliquez sans shifter c'est le contenu du fichier qui apparaît.

Une solution plus rapide consiste à télécharger tous les programmes en une seule fois au moyen du fichier compressé antirouille.tgz .

Si tous les exemples sont en C++, on s'est permis de renommer certains mots-clés et d'utiliser certaines fonctions, afin de faire apparaître des concepts plus généraux, des accessoires pratiques, et d'éliminer le côté rébarbatif  du langage. Tout ceci étant défini dans le fichier definitions.h  On reconnaitra ces termes car ils commencent tous par une majuscule. Tous les mots-clés ou les noms de fonctions en minuscules font partie du langage C++.

Bonne route.

Retour au début

oUn premier programme

Vous pouver télécharger les fichiers sources : hello.cpp et definitions.h .

Le programme suivant  écrit une phrase sur votre écran.

  1. // C++ : Mon premier programme

  2. #include "definitions.h"

  3. main ()
  4. {                 /* Si vous comprenez l'espagnol  */
  5.    Print (" Ola Hombre");
  6. }

Si vous téléchargez ce programme et le fichier definitions.h, que vous le compilez avec un compilateur c++, et que vous lancez le code exécutable, vous verrez s'afficher sur votre écran :

  Ola Hombre

Quelques explications :

Retour au début


oLes déclarations
 

o    Déclaration de variables

Un programme est une liste d'instructions permettant de manipuler des variables. Une variable est nommée au moyen d'un identifieur. Un identifieur est une suite de lettres ou de chiffres ou du caractère "_" (nommé underscore ou blanc souligné). Un identifieur doit commencer par une lettre. Dans les langages les plus anciens, (Fortran et Pascal), les majuscules et les minuscules sont équivalentes. En C/C++/Java les mots-clés du langage sont en minuscules.  On se sert du blanc souligné ou de l'alternance majuscule/minuscules pour définir des noms composés. Les caractères accentués sont interdits. 

Identifieurs corrects       :  n, nombre_de_trucs, NomDuTruc, a386, OM, PSG.
Identifieurs incorrects     :  2be3 (commence par un chiffre) , nombre_d'éléphants (à cause des accents et de l'apostrophe)

Une variable possède un type. Ce type est défini lors de la déclaration.

Avant d'être utilisée, une variable doit préalablement être déclarée. C'est lors de la déclaration que l'on nomme les variables qui vont être utilisées, que l'on leur associe un type et éventuellement une valeur initiale.

      type  identifieur = valeur initiale;
ou
       type  identifieur ;

On va décrire deux types fondamentaux  : entiers et réels. Les autres types seront décrits au fur et à mesure du cours.

o    Déclarations d'entiers

Pour les implémenter les variables entières, on utilisera le type int  standard C/C++ ou Java.

  int  marignan = 1515;
  int rayon        = 24;
  int diametre    = 2*rayon;

Remarques générales concernant les déclarations :
o    Déclarations de réels

Il s'agit  d'implémenter des nombre réels, (plus exactement un sous-ensemble des rationnels) sous a forme classique (mantise, exposant). On utilisera le type standard C float. Au niveau de la constante réelle, le point sert à délimiter la partie décimale. L'exposant (puissance de 10) se note au moyen de la lettre "e".  Ainsi 2e+3 correspond à deux dix puissance 3 et vaut 3000.

    float   pi       = 3.14159;
    float   kilo    = 1.0e+3 ;
    float   micro = 1.0e-6;

Il existe une autre dénomination de reels  en C/C++/Java : double qui occupe plus de place et qui permet une précision plus grande.

Retour au début


oLes instructions de base
 

o    Les affectations

Il s'agit de modifier le valeur d'une variable. Sa syntaxe est :

variable = expression;

Exemples :

resultat     = (a+b) / (a-b);
delta        = b*b - 4*a*c;
moyenne  = (a+b+c)/3;
c1            = cos (theta);
compteur = compteur + 1;

 
o    Appels de fonctions et de procédures

Un appel de fonction permet de lancer un traitement quelconque et éventuellement d'en récupérer un résultat. Cette instruction permet en une ligne d'effectuer un traitement qui pourrait éventuellement avoir été réalisé en une ou plusieurs milliers de lignes. L'appel de fonction permet (devrait permettre) de regrouper les instructions d'un logiciel en des programmes de taille raisonnable qui devraient donc rester clairs et lisibles.

Un appel de fonction se reconnait à la présence de parenthèses qui suivent un identifieur. Entre ces parenthèses figurent zéro ou plusieurs arguments qui constituent l'information transmise à la fonction par le programme appelant.

      reponse = ma_fonction (arg1, arg2, ....argn);
ou
       ma_fonction (arg1, arg2, ....argn);

On a coutume d'appeler procédure une fonction qui ne rend pas de valeur. On a vu la procédure Print dans le programme hello.cpp. Attention (C/C++)  : même s'il n'y a pas d'argument, ne pas oublier les parenthèses. Cette erreur n'est pas signalée par le compilateur C et provoque seulement un avertissement en C++. Le problème est que dans les deux cas, l'appel de  procédure  n'est pas exécuté.

Pour l'anecdote, en Pascal une procédure sans argument n'a pas besoin de parenthèses. En FORTRAN une procédure se nomme subroutine. Elle est appelée au moyen du mot-clé call.

Pour accéder à une fonction, le compilateur doit contrôler le nombre et le type des arguments utilisés. En C et C++, une fonction doit être déclarée. On indique le type de valeur qu'elle rend  ainsi que le type de chacun de des arguments. On appelle cette déclaration l'entête ou  le prototype de la fonction. En général le prototype des fonctions utilisées figure dans les includes appelés au début de programme. Les langages les plus récents, C++ et Java autorisent l'utilisation d'un même nom de fonction associé à des arguments de type différents. On peut donc appeler plusieurs variantes d'une fonction ayant un même nom, mais une signature différente.

      type_f  ma_fonction (type1 arg1, type2 arg2, ..., typen argn);

La présence des entêtes des fonctions utilisées n'est pas forcémént fiable : on est toujours à la merci d'une fonction qui évolue et dont on oublie de modifier le prototype. Il existe d'autres d'autres solutions que le prototypage des fonctions utilisées. En Java ou en FORTRAN 90, pour connaître le nombre et le type des arguments utilisés on accède directement à de l'information issue de la compilation des fonctions utilisées. En FORTRAN 77, on n'a pas de moyen de déclarer les arguments des fonctions appelées car le compilateur ne le contrôle pas.

Exemples de prototypes des fonctions utilisées dans les exemples de cette page :

      Procedure Print (Cstring message);     
      Procedure
Print (float   valeur);     
      Procedure Print (int     valeur);

      Procedure Display (float variable);   // Affiche une variable
      Procedure Display (int    variable);  //    nom = valeur
      Procedure Display (Cstring variable);       

      int   ReadInteger (); // Saisit un entier à la console
      float ReadReal ();    // Saisit un réel à la console

      int   Modulo (int i, int j); // renvoie i modulo j


En C, C++, Java c'est le mot-clé void qui en fait est utilisé pour Procedure.

o    Blocs d'instructions

Un bloc d'instructions est constitué de plusieurs instructions  regroupées entre deux accollades. Elles  seront utilisées dans les instructions qui suivent, par exemple pour les itérations. Elles servent aussi à définir des variables locales au bloc.

     {
      int val = 1;
      instruction;
      instruction;
      instruction;
      instruction;
      }

Ici la variable val est valide à l'intérieur du bloc.

o    Instructions conditionnelles

Le test complet :

  if (condition)
          instruction;
      else
          instruction;

Le test simple ne possède pas de branche else :

  if (condition)
      instruction;

Les instructions peuvent être, et sont la plupart du temps des blocs d'instructions.

  if (a<b)
          {
           mini  = a;
           mani = b;
           }
      else
           {
           mini  = b;
           mani = a;
           }

Enchaînement de tests :

  if (note>=15)
          Print ("Bravo Hélène");
      else if (note>=10)
          Print ("Pas mal Nico");
      else
          Print ("Tu ne t'arranges pas, Alexandre ..");

Convention  :  Le "else" se rapporte au dernier "if" ouvert :
Exemple

  if (c1)
     if (c2)
           instruction_1;
      else
           instruction_2;

est équivalent à :

  if (c1)
     {
      if (c2)   instruction_1;
          else  instruction_2;
      }

et non pas à :

  if (c1)
         {
          if (c2) instruction_1;
          }
      else
          instruction_2;
     

Conclusion : en cas de doute, utilisez des accollades. Utilisez les systématiquement d'ailleurs.

Ada et FORTRAN 77 ont levé cette ambiguïté en "fermant le test" au moyen du mot-clé endif. Ils ont aussi introduit le mot-clé intermédiaire elseif permettant de définir des cascades de tests.

o    L'expression conditonnelle

L'expression conditionnelle peut utiliser les opérateurs relationnels suivants : <, <=, >, =>, == (égalité), != (différent).

Attention : une erreur très fréquente en C/C++ consiste à confondre l'opérateur d'égalité "==" avec l'opérateur d'affectation "=". L'ennui, c'est que le compilateur ne détecte pas cette erreur. En effet les multiples possibilités syntaxiques du C donnent un sens à cette fformulation.

On peut aussi utiliser les opérateurs logiques et (And) , ou (Or), non (Not).
Exemple :

  if (note1 > note_eliminatoire And note2>note_eliminatoire
                                              And
(note1+note2)>=20 )
     {
      Print ("Félicitations vous etes recus");
      moyenne =( note1+note2)/2;
     }

Remarque  : En C/C++/Java les opérateurs Or et And fonctionnent selon le principe de "l'évaluation progressive" appelée aussi "évaluation paresseuse" :

o    Instructions itératives

o    Boucle "while"

Cette boucle effectue l'instruction tant que la condition est vraie :

    while (condition)
             corps_de_la_boucle;

Exemple (fichier facteurs.cpp) . Cet algorithme affiche la liste des facteurs premiers d'un nombre :


 // C++ : Decomposition en facteurs premiers
#include "definitions.h"
main ()
{
   int valeur = ReadInteger();
   Display (valeur);
   int reste    = valeur;
   int facteur = 2;

   while (reste>facteur)
        {
        if (Modulo (reste,facteur)==0)
              {
              Display (facteur);
              reste = reste / facteur;
              }
           else
              {
              facteur = facteur + 1;
              }
        }
   Display (reste);  
}


o    Boucle "for"

    for (instruction_depart ; condition ; increment)
           corps_de_la_boucle;

Cette syntaxe est propre à C/C++/Java. Elle est une forme condensée de :

    instruction_depart;
    while (condition )
              {
               corps_de_la_boucle;
               increment;
               }

Elle est utilisée fréquemment pour faire varier un indice. Par exemple :

    for (int nro=0 ; nro<nombre ; nro++) 
           corps_de_la_boucle;

Notons a présence de l'opérateur d'incrément  ++. nro++ est une forme condensée de nro=nro+1. Les formes condensées spécifiques à C ne font pas partie de ce cours, mais ce type de boucle est  trop systématiquement employé sous cette forme pour qu'on ne fasse pas une exception. Notons aussi le zéro comme point de départ et l'inégalité stricte par rapport au nombre font aussi partie des habitudes issues de C. Voir aussi les indices de tableaux.

o    Boucle "do ... while"

Comme la boucle while, sauf que le corps de la boucle est exécuté au mois une fois.

   do
       instruction;
       while (condition);
  


o    Sélection

Il s'agit d'appliquer un traitement sélectif pondéré par une valeur de type entier ou assimilé, comme les caractères ou les énumérations. L'instruction a la forme suivante :

   switch (expression_entiere)
       {
       case val1 :
               instructions ;
               break;
       case val2 :
               instructions ;
               break;
       default  :
               instructions ;
       } 

Remarques :
Exemple :
   switch ( jour)
       {
       case 1 : Print ("Lundi");
                     break;
       case 2 : Print ("Mardi");
                     break;
       case 3 : Print ("Mercredi");
                     break;
       case 4 : Print ("Jeudi");
                     break;
       case 5 : Print ("Vendredi");
                     break;
       case 6 :
       case 7 : Print ("C'est week-end !!");
                     break;
       default  :
                     Print (" Erreur dans les données");
       } 

Retour au début


oLe pré-processeur

Si le compilateur analyse le contenu d'un programme, il existe un mécanisme activé avant la compilation proprement dite. Ce mécanisme permet de définir des symboles (directive #define)  ou d'inclure (directive #include) le contenu de fichiers sources à un endroit donné. On peut aussi tester la définition de symboles (#ifdef ou #ifndef ...#endif).

Exemple :
#include "definitions.h"

#ifndef  PI
#define PI       3.14159
#endif

#define Message "Vois avez gagné"

#define Carre(a)  ((a)*(a))

Le mécanisme de définition fut pendant longtemps le seul moyen de définir des constantes en C. Il est d'ailleurs utilisé asssez abusivement dans ce cours pour éliminer certaines syntaxes C++ trop lourdes ou trop consommatrices d'informations non essentielles. Attention cependant : le compilateur ne "voit" pas les symboles, il ne voit que leur forme substituée.

On peut aussi définir des formules par exemple Carre. Notez les parenthèses. Si par exemple on avait écrit #define Carre(a) a*a, alors Carre(x-1) serait développé en x-1*x - 1, c'est à dire -1, un comble pour un carré !

Le mécanisme d'inclusion est très utile, voir indispensable en C/C++, Fortran 77, Pascal,  pour partager de l'information entre plusieurs programmes. Et aussi pour condenser en une seule ligne une grande quantité d'informations. Par exemple l'ensemble des déclarations (fonctions, constantes, ...) système ou communes à un projet ou à un thème donné.

Par exemple en C/C++, si vous utilisez des fonctions de lecture/ écriture sur des fichiers texte, vous devez inclure le fichier stdio.h. Pour ce cours, qui n'est pas un cours de C, les fichiers systèmes utiles sont déjà inclus dans le fichier definitions.h.  Vous pouvez donc ignorer ce genre de détail d'implémentation.

Attentions ce ne sont pas des "instructions" du C, mais des directives lues par un programme qui travaille avant le compilateur. Il n'y a donc pas lieu de les terminer par un point-virgule.

En Java, il n'existe pas de pre-processeur. Comme en FORTRAN 90, le compilateur accède directement à des fichiers compilés pour effectuer ses vérifications. C++ a décidé de remplacer autant que faire se peut les instructions #define par des constantes ou des fonctions en ligne.

En C/C++, il est possible que les includes soient imbriqués. Aussi lorsqu'on crée un include, il est indispensable de mettre en place un mécanisme de "double inclusion"  qui a la forme suivante (voir le fichier  definitions.h)  :

Exemple :

// Incl: Redefinitions generales
#ifndef _DEFINITIONS_H
#define _DEFINITIONS_H

#include <stdio.h>
#include <string.h>

. . . . . . . . . . .
#endif


Retour au début


oAutres déclarations

o     Les Constantes (const)

En fait il s'agit d'un attribut que l'on peut accoller à tout type de variable. Utiliser une constante permet avant tout de dimensionner un tableau. Le compilateur dans certains cas peut empêcher la modification d'une constante. On cherche à améliorer la fiablilté des programmes.

La syntaxe est la suivante :

   const float pi             = 3.14159;
   const int    revolution = 1789;

o    Les Enumérations (enum)

Il s'agit de définir un type qui  associe à une variable un nombre fini de valeurs.  La syntaxe est la suivante :

   enum   nom_du_type  {VAL0, VAL1, VAL2, ..., VALn};
    nom_du_type   var1, var2;

Les valeurs VAL0, VAL1, ... sont équivalentes à des constantes entières qui se succèdent en partant de zéro.

Exemple :
    enum   m  {UnPeu, Beaucoup, Passionnement};
    enum   enum_cardinal { Nord, Est, Sud, Ouest };
    enum_cardinal  dir1, dir2;

Des variables de type enum_cardinal confèrent plus de sens et  une plus grande lisibilité au programme qui les utilise. On comprend mieux une instruction "if (dir1==Sud)" que " if (dir1==2)"

La première ligne est équivalente à :
 
    const int  UnPeu              = 0;
    const int  Beaucoup         = 1;
    const int  Passionnement  = 2;
Ou bien :
#define UnPeu               = 0;
#define  Beaucoup         = 1;
#define  Passionnement  = 2;

o    Les booléens (bool)

Une variable  booléenne a deux états : vrai ou faux. En C++ et en Java, le type se nomme bool, les deux constantes booléennes sont  true et false.

    bool   ouvert       =   true ;
    bool   xdepasse   =  x > maxi;
    bool   ydedans    =   y >= a And y <= b;

En C Ansi, il n'y a pas de type booléen. Il est assimilé au type entier. Une extression entière est considérée comme vraie si elle est différente de 0.  Les constantes booléennes sont en principe 1 ou 0. En fait, les (bons) programmeurs redéfinissent un type  booléen au moyen d'une énumération ou de symboles : enum bool {true,false}.

o    Les tableaux

Un tableau est un ensemble de variables scalaire accessibles au moyen d'un  indice placé entre des crochets.
Lors de la déclaration on indique sa dimension au moyen d'une constante. Un tableau peut être initialisé lors de sa déclaration au moyen d'une liste d'éléments placés entre deux accolades.

On prendra dans ce cours la convention C/C++/Java, selon laquelle l'indice d'un tableau commence à zéro et doit être strictement inférieur à sa dimension. Attention, le dépassement d'indice de tableau est une erreur très fréquente.
En FORTRAN et en Pascal, , par défaut l'indice varie de 1 à n inclus, mais on peut redéfinir l'indice minimum.

Exemple :

    const int   dim_table = 10;
    const int   nbr_coeff = 3;

    int            carre[dim_table];
    float         coeff [nbr_coeff] = { 1.1,  2.3,  3.5 };
 
    for (int nro=0; nro<dim_table; nro++)
          carre [nro] = nro*nro;

Pour  implémenter des tableaux à plusieurs dimensions (des matrices), en C/C++/Java on définit des tableaux de tableaux.
Exemple :

    const int   nbr_cols  = 3;
    const int   nbr_ligs   = 2;

    int  matrix [nbr_ligs]  [nbr_cols]  =  { {11,  12,  13}
                                                             {21,  22,  23} };

Remarques :


o    Les caractères (char)

Une constante de type caractère est constituée d'un caractère délimité par deux simples quotes. En C et  C++ le type caractère est noté char.

    char   lettre          =  'a';
    char   pourcent    =  '%';
    char   quote        =  '\'';
    char   antislash    =  '\\';
    char   carnul       =  '\0';

Notons que pour utiliser la quote entre quotes, il faut la faire précéder de l'antislash "\". Et pour faire apparaître l'antislash, on double l'antislash. En Fortran, on double la quote.

En fait un caractère est équivalent à un entier variant de 0 à 255 correspondant au numéro (nommé code ascii) du caractère. Par exemple le code ascii de 'a' (minuscule) vaut 97, celui de 'A' (majuscule) vaut 65 et celui du caractère '0' (zéro) vaut 20. Il est donc permis d'écrire :

    int   code_0       =  '0';

    Display (code_0);

Le résultat du programme est :

    ...  code_0 = 48;


o    Les chaînes de caractères (Cstring)

On notera ce type Cstring (notation propre à ce cours, définie dans le fichier definitions.h). On peut affecter une Cstring au moyen d'une constante de type chaîne de caractères. Une telle constante est constituée d'un ensemble de caractères délimitée par deux doubles quotes. Si une double quote ou un antislash figurent dans la chaîne, on les fait précéder d'un antislash ("/").
.
On peut aussi affecter une chaîne de caractères avec l'adresse nulle : NULL.

    Cstring   bateau         =  "titanic";
    Cstring   message      =  "Tiens, un iceberg !!";
    Cstring    yacht          =   bateau;
    Cstring    hasa_quote =   "My name is \"nobody\" ";
    Cstring    canot          = NULL;

C/C++/Java permettent de gérer les chaînes trop longues grâce au mécanisme des "chaînes adjacentes". Si deux chaînes ne sont séparées que par par des "espaces" (blancs, tabulations ou sauts de ligne), elles sont fusionnées.

    Cstring   barre         =  "__________________________________________"
                                         "__________________________________________";


En C/C++, le contenu d'une chaîne de caractères est équivalent à un tableau de caractères qui se termine par le caractère nul. On peut donc aussi affecter une Cstring au moyen d'un tableau de caractères. On peut aussi
appliquer à ce tableau, en plus des opérations classiques des tableaux,  toutes les fonctions dédiées aux chaînes de caractères.  Réciproquement on peut accéder au ième caractère d'une chaîne au moyen de l'opérateur "crochets". Notons que l'on ne peut modifier le contenu d'une chaîne de caractères définie par une constante.

Suite à la convention qui veut que la chaîne est délimitée par un caractère nul supplémentaire, une chaîne de N caractères doit tenir dans un tableau de taille N+1. Si ce caractère nul est absent, manipuler une chaîne "mal terminée" risque de provoquer un arrêt brutal du programme.

En Fortran le délimiteur est la simple quote et il n'existe pas de distinction entre caractère et chaîne de caractères. Un caractère est une chaîne de longueur 1.

    const int size_string    = 31;
    char         tabchar [size_string+1];
    Cstring    chn = tabchar


Fonction
Rôle
int strlen (Cstring chaine) Donne la longueur d'une chaîne
(nombre de caractères avant le caractère nul)
strcpy (Cstring dest, Cstring orig)
Copie la chaine orig dans dest.
strcat (Cstring dest, Cstring plus) Place la chaîne plus à la fin de la chaîne dest.
bool Cestegal (Cstring c1, Cstring c2) Egalité de chaînes de caractères
int strcmp (Cstring c1, Cstring c2) Comparaison de chaînes <0 si c1<c2, 0 si c1=c2, >0 sinon
sprintf (Cstring dest, Cstring format, values...) Print formatté sur chaîne de caractères (Cf Fichiers)

Précautions à prendre lors des manipulations de chaînes de caractères :

o    Les structures (struct)

Une structure C/C++ est une donnée constituée de la juxtaposition de données appelées membres. Les membres peuvent être des scalaires, des tableaux ou d'autres structures. (En Pascal on parle de record, en FORTRAN 77 on n'en parle pas).

    struct str_complex {
              float reel;
              float imag;
              };
                             // Ce type est utilisé de la façon suivante :
      str_complex   un = { 1, 0};
      str_complex   i   = { 0, 1};
      str_complex   z1, z2;
                            // On accède aux champs de la façon suivante :
      z1.reel   = 36;
      z1.imag = 15;

      z2   =  z1;         // Copie champ à champ.

L'affectation permet de copier une structure sur l'autre. La copie se fait champ à champ.

La syntaxe C++ est allégée par rapport à C Ansi. Il fallait écrire "struct st_reel z;". Et il n'y a pas de copie de structure.

Retour au début



oLes fichiers texte (Filetext)

On notera ce type Filetext (notation propre à ce cours, définie dans le fichier definitions.h). Il existe des fichiers non formattés ou binaires ou acccès direct qui ne seront pas traités dans ce cours.
Un fichier est initialisé au moyen de l'ouverture d'un fichier. La fonction fopen est utilisée à cet usage. Un fichier C est un pointeur. Il peut être initialisé avec la valeur NULL pour indiquer que la variable ne correspond à aucun fichier ouvert.

    Filetext   fic   =  fopen ("monfichier.txt", "r");

Il existe deux fichers prédéfinis (et déja ouverts) : stdin pour la lecture au clavier, stdout pour l'écriture à l'écran.

Quelques fonctions pour gérer les fichiers :

Fonction
Rôle
Filetext fopen (Cstring nom, Cstring mode) Ouvre un fichier.  mode vaut "r" pour un fichier ouvert en lecture, "w" pour un fichier ouvert en écriture.
fclose (Filetext fic)  Ferme un fichier ouvert
fprintf  (Filetext fic, Cstring format, values, ....) Procédure d'impression formattée sur un fichier
printf  (Cstring format, values, ....) Procédure d'impression formattée sur la console
Print  (value)
Imprime la valeur (ebtière, réelle, chaîne) et passe à la ligne.
Display (expr)
Ecrit  une ligne de la forme : ... expr = valeur

Exemple d'utilisation de la procédure d'impression formattée :

   int    nombre =  0;
   float value = 0;

   Filetext ft = fopen ("result.log", "w");
  ........................................................;
   fprintf (ft, "Nombre d'iterations : %d, le résultat vaut %g \n", nombre, value);
   fclose (ft);

Le principe de l'écriture formattée (en C, Fortran) est le suivant :

Voici les principaux métacaractères :

Metasymbole
Signification
\n Saut de ligne. A placer génératement en fin de format
\t
Caractère de tabulation
\"
Pour placer une quote
%d Ecriture d'un entier
%g Ecriture d'un réel
%s Ecriture d'une chaîne de caractères
%c
Ecriture d'un caractère
%%
Pour écrire la caractère %

Retour au début


oLes fonctions et les procédures
 
o    Généralités

On a vu comment appeler une fonction. On va voir comment en définir une.
Il suffit d''associer à un entête de fonction un corps de fonction délimité entre accollades. La valeur esr retournée au programme principal au moyen du mot-clé return.


  1. // C++ : Un appel de fonction
  2. #include "definitions.h"
  3. // ====================================   Fonction Moyenne
  4. float moyenne (float a, float b)
  5. {
  6.     float    moy = (a+b)/2;
  7.     return moy;
  8. }
  9. // ====================================  Programme principal
  10. main ()
  11. {                                  
  12.     float x =10;
  13.     float y = 15;
  14.     float  m = moyenne (x,y);
  15.     Display (m);
  16. }

L'entête de la fonction, contrairement au prototypene se termine pas par un point-virgule, mais est immédiatement suivie du corps de la fonction.

Lors d'un appel de fonction  (ligne 14) la démarche est la suivante :
o    Arguments variables

En C/C++, les arguments sont copiés. On parle de passage d'argument par valeur. Aussi, si l'on modifie l'un d'entre eux, c'est leur copie qui est modifiée. Alors que les arguments effectifs du programme appelant sont inchangés.Pour pouvoir modifier les arguments, on doit effectuer un passage d'argument par adresse (ou par référence) au moyen de l'opérateur & :


  1. // C++ : Passage d'arguments par référence

  2. #include "definitions.h"

  3. // ====================================   permuter
  4. Procedure permuter (float &gauche, float &droite)
  5. {
  6.     float   tampon = gauche;
  7.     gauche  = droite;
  8.     droite    = tampon;
  9. }
  10. // ====================================  Programme principal
  11. main ()
  12. {                                  
  13.     float   x =10;
  14.     float   y = 15;

  15.     permuter (x, y);
  16.     Display (x);
  17.     Display (y);
  18. }

Hormis l'opérateur & devant les arguments formels, rien ne change. Le compilateur gère les adresses des variables dans le programme appelant et le programme appelé. Contrainte : On ne peut passer des constantes ou des expressions par adresse, et on ne peut effectuer de conversion d'argument. Si un argument passé par adresse est de type réel, on ne peut lui fournir une variable entière.  Exemples :


// C++ : Passage d'arguments par référence : erreurs
#include "definitions.h"

Procedure permuter (float &gauche, float &droite);

main ()
{
    float             x = 10;
    const float   x = 12;
    int                n =  15;

    permuter (x,  n);          //  Incorrect, n est une variable entière
    permuter (x,  y);          //  Incorrect, y est une constante
    permuter (x, 3.14);      //  Idem
    permuter (x,  x+1);      //  Incorrect, x+1 est une expression
}

En Fortran 77, tous les passages d'argument se font par adresse. en C Ansi, le passage par adresse se fait au moyen de pointeurs, ce qui alourdit un peu les notations. En Pascal, c'est le mot clé var qui remplace l'opérateur &.


o    Tableaux passés en argument

En C/C++ (dans les autres langages aussi d'ailleurs) un tableau est équivalent à l'adresse du premier élément de ce tableau.
L'opérateur & n'est donc pas utile. L'instruction  appel_fonction(table)  est équivalente à appel_fonction(&table[0]).
 Il n'est pas non plus nécessaire de préciser sa dimension.. En effet, si le tableau  est déclaré dans le programme appelant et dans la fonction, il n'est défini (associé à un emplacement mémoire) que dans le programme appelant.  Exemple :

// C++ : Exemple de boucle
#include "definitions.h"
// ========================== main
int remplir (int nombre, int table[])
{
   for (int ii=0; ii<nombre; ii++)
       {
       table[ii] = ii*ii;
       }
}
// ========================== main
int main ()
{
   const int size = 10;
   int table [size];
   remplir (size, table);
   Display (table[5]);
}

.
o    Variables rémanentes (static)

En général, lors d'un appel de fonction, les variables de cette fonction sont réinitialisées. On dit qu'une variable est rémanente si sa valeur est mémorisée d'un appel à l'autre. On s'en sert par exemple pour implémenter un compteur d'appels. Exemples :

// C++ : Passage d'arguments par référence : erreurs
#include "definitions.h"
// ================================ cumuler
float cumuler (float ajout)
{
    static  float bilan = 100;
    static  bool premiere_fois = true;
     if (premiere_fois)
        {
         premiere_fois = false;
         printf  ("Premier appel : somme initiale = %g Euros\n", bilan)
        }
    bilan = bilan + ajout;
    return bilan;
}
// ================================ main
main ()
{
    bilan (30);
    bilan (-20);
    bilan (10);
}

En Fortran 77, c'est l'instruction save qui permet de préserver la valeur d'une variable d'un appel à l'autre.

o    Exemple de fonctions : calcul du pgcd

Lorsqu'on appelle une fonction dans une même fonction, on parle de fonction récursive. (impossible en Fortran 77). Exemple :  (fichier pgcd.cpp)


// C++ : Exemple de fonction récursive
#include "definitions.h"
// ======================================== calculer_pgcd
int calculer_pgcd (int v1, int v2)
{
   if (v1<=0 Or v2<=0) return 0;
   if (v1==v2)                return v1;
   int pgcd;
   if (v1<v2)  pgcd = calculer_pgcd (v1,      v2-v1);
      else        pgcd = calculer_pgcd (v1-v2, v2);
   return pgcd;
}
// ======================================== main
int main ()
{
   int v1 = 7*5*11*13;
   int v2 = 7*2*2*6*7;
   Display (v1);
   Display (v2);
   float pgcd = calculer_pgcd (v1, v2);
   Display (pgcd);
}


Retour au début


oPortée des variables
 
o    Dans un même fichier

Considérons un fichier source contenant plusieurs fonctions. En résumé :

// C++ : Gestion d'une liste, variables globales
#include "definitions.h"

const int pas_trouve = -1;
enum  enum_errr { OK, ERR, ERR_TROP, ERR_DEJALA};

const int       max_eleves = 20;
Global Cstring  tab_noms  [max_eleves];
Global float    tab_notes [max_eleves];
Global int      nbr_eleves = 0;

// ============================================  trouver_eleve
int trouver_eleve (Cstring nom)
{
   for (int nro=0; nro<nbr_eleves; nro++)
      if (Cequals (nom, tab_noms[nro]))
         return nro;
   return pas_trouve;
}
// ============================================ ajouter_eleve
int ajouter_eleve (Cstring nom, float note)
{
   if (nbr_eleves>=max_eleves) return ERR_TROP;
   int nro = trouver_eleve (nom);
   if (nro>=0)                            return ERR_DEJALA;

   tab_noms  [nbr_eleves] = NewString (nom);
   tab_notes [nbr_eleves] = note;
   nbr_eleves = nbr_eleves + 1;
   return OK;
}
// ============================================ imprimer_liste
Procedure imprimer_liste ()
{
   Print (".... Impression de la liste");
   Display (nbr_eleves);
   for (int nro=0; nro<nbr_eleves; nro++)
      {
      printf ("   %s     : %g\n", tab_noms[nro], tab_notes[nro]);
      }
}
// ============================================ saisir_notes
Procedure saisir_notes ()
{
   const int size_rep = 63;
   char reponse [size_rep+1];

   while (true)
         {
         int lg = ReadString (reponse, "Entrez un nom : ", size_rep);
         if (lg<=0) return;

         float note = ReadReal ("Sa note ? ");
         ajouter_eleve (reponse, note);
         }
}
// ============================================ Main
int main ()
{
   saisir_notes ();
   imprimer_liste ();
}



o    Dans plusieurs fichiers (variables externes)

Il est possible d'accéder à la même variable à partir de plusieurs fichiers source. On leurs associe l'attribut extern. Si la déclaration multiple de la même variable dans plusieurs fichiers source ne pose pas de problème, il est cependant nécessaire que sa définition, c'est à dire son emplacement mémoire soit unique. Exemple, premier fichier :

  1. // C++ : Premier fichier
  2. #include "definitions.h"

  3. extern String son_nom;
  4. .....................................................................................

Second fichier :

  1. // C++ : Second fichier
  2. #include "definitions.h"

  3. extern String son_nom;
  4. String             son_nom = "Wolfgang Amadeus";        // Valeur initiale
  5. ......................................................................................

L'attribut Global utilisé précédemment permet d'autoriser des variables homonymes dans plusieurs fichiers sans qu'elles n'interfèrent. C'est une protection.

o    Les variables externes ne sont pas indispensables

Utiliser des variables externes comporte quelques risques. En fait, une variable externe peut être modifiée dans n'importe quel programme. Ce qui dans un logiciel de 300 000 lignes peut effrayer certains chefs de projet paranoïaques. Dont votre serviteur fait partie.

On peut toujours remplacer une variable externe par une variable globale déclarée dans un seul fichier à laquelle on associe une fonction d'interrogation (ligne 4), éventuellement une fonction de modification (ligne 11).
Exemple, premier fichier :

  1. // C++ : Premier fichier
  2. #include "definitions.h"

  3. Global String son_nom  = get_his_name ();
  4. .....................................................................................
  5. set_his_name ("Ludwig Von");

Second fichier :

  1. // C++ : Second fichier
  2. #include "definitions.h"

  3. Global String son_nom = "Wolfgang Amadeus";        // Valeur initiale
  4. // ============================== Interrogation
  5. String  get_his_name ()
  6. {
  7.    return son_nom;
  8. }
  9. // ============================== Modification
  10. Procedure set_his_name (String hisname)
  11. {
  12.   son_nom = hisname;
  13. }
  14. ......................................................................................

Retour au début


oLes pointeurs et l'allocation programmée
 
Allouer une variable consite à associer une adresse de variable à un emplacement mémoire fourni par le système
Lors des déclarations de variables (structures ou tableaux dont on connait la taille), cette allocation est statique car la taille du tableau est connue à la compilation.  Or on peut souhaiter dimensionner les tableaux ou créer des variables  au moment de l'exécution. C'est le mécanisme d'allocation programmée ou d'allocation dynamique.

Mettre en place une allocation dynamique se fait en  plusieurs étapes :
oAllocation dynamique de structures

Voyons sur un exemple la démarche appliquée à une structure :

  1.     struct str_complexe {
  2.               float reel;
  3.               float imag;
  4.               };
  5.                                                     // Déclaration d'un pointeur d'un type donné
  6.       str_complexe*   adrz = NULL ;        
  7.                                                     // Allocation d'une variable
  8.       adrz = new str_complexe;
  9.                                                    // Utilisation d'une variable allouée 
  10.       *(adrz).reel     = 1;
  11.                                                    // notation condensée : accès à un champ
  12.                                                    // à partir d'un adresse de structure
  13.        adrz->imag    = 1; 
  14.                                                    // On libère la mémoire :
  15.       delete adrz;
  16.       adrz   = NULL;      // Pour éviter d'accéder à un emplacement mémoire illicite

Quelques explications  :

Si la notation étoilée vous pose des problémes, vous pouvez toujours définir des symboles :

#define Pointerof(type)    type*
#define Valueof(addr)     *addr


 o Allocation dynamique de tableaux

Exemple pour un tableau :


  1.                                                     // Déclaration d'un pointeur
  2.       int*  tabdyn = NULL ;
  3.                                                     // Saisie d'une valeur entière
  4.       int taille = ReadInteger ();
  5.       if (taille < 2)  taille = 2;
  6.                                                    // Allocation dynamique du tableau
  7.       tabdyn= new int [taille];
  8.                                                    // Utilisation d'une variable allouée.
  9.       for (int nro=0; nro<taille; nro++)
  10.             tabdyn [nro] = 0;
  11.                                                    // Libération de  la mémoire :
  12.       delete [] tabdyn;

Quelques commentaires :
Remarques :
o Exemple : gestion d'une liste chaînée

Ce programme (liste_fifo.cpp) est une application directe de l'allocation dynamique. Il s'agit de créer une liste "fifo" ( First In, First Out, c'est à dire qu'on place le dernier arrivé en fin de liste).


  1. // C++ : Gestion d'une liste chaînée
  2. #include "definitions.h"
  3. struct str_maillon
  4.        {
  5.        Cstring nom;
  6.        str_maillon* suivant;
  7.        };
  8. // =========================================== creer_liste
  9. str_maillon* creer_liste ()
  10. {
  11.    const int  size_rep = 63;
  12.    char reponse [size_rep+1];
  13.    str_maillon* tete    = NULL;
  14.   str_maillon* queue  = NULL;
  15.    while (ReadString (reponse, "Entrez un nom : ", size_rep)>0)
  16.          {
  17.                                        // Creation d'une copie de chaine
  18.          int lg = strlen (reponse);
  19.          Cstring name = new char [lg];
  20.          strcpy (name, reponse);
  21.          str_maillon* elt = new str_maillon;
  22.          elt->nom     = name;
  23.          elt->suivant = NULL;
  24.          if ( tete==NULL) tete              = elt;
  25.             else                   queue->next = elt;
  26.          queue = elt;
  27.          }
  28.    return tete;
  29. }
  30. // ========================================== imprimer_liste
  31. Procedure imprimer_liste (str_maillon* tete)
  32. {
  33.    Print (".... Impression de la liste");
  34.    for (str_maillon* elt=tete; elt!=NULL; elt=elt->suivant)
  35.        Print (elt-> nom);
  36. }
  37. // ========================================== detruire_liste
  38. Procedure detruire_liste (str_maillon* tete)
  39. {
  40.    Print ("....Destruction de la liste");
  41.    str_maillon* next = NULL;
  42.    for (str_maillon* elt=tete; elt!=NULL; elt=next)
  43.        {
  44.        next = elt->suivant;
  45.        delete elt-> nom;
  46.        delete elt;
  47.        }
  48. }
  49. // ========================================== Main
  50. int main ()
  51. {
  52.    str_maillon* tete = creer_liste ();
  53.    imprimer_liste (tete);
  54.    detruire_liste  (tete);
  55. }


 o Utilisation de pointeurs dans la passage d'arguments  variables

On a vu que pour pouvoir modifier un argument d'une procédure, il fallait indiquer dans l'en-tête de la procédure que l'on effectuait un passage par adresse au moyen de l'opérateur & (ligne 4).


  1.    // C++ : Exemple de fonction récursive
  2. #include "definitions.h"
  3. // ======================================== motif_value
  4. Procedure motif_value (int &value)
  5. {
  6.   value = 1515;
  7. }
  8. // ======================================== main
  9. int main ()
  10. {
  11.    int v1 = 0;
  12.    modif_value (v1);
  13.    Display (v1);
  14. }

On peut obtenir un effet équivalent au moyen d'un pointeur.

  1. // C++ : Exemple de fonction récursive
  2. #include "definitions.h"
  3. // ======================================== motif_value
  4. Procedure motif_value (int* value)
  5. {
  6.   *value = 1515;
  7. }
  8. // ======================================== main
  9. int main ()
  10. {
  11.    int v1 = 0;
  12.    modif_value (&v1);
  13.    Display (v1);
  14. }

Quelques commentaires :
Cela nécessite plus de manipulations que la technique précédente. C'était cependant la seule disponible en C.

Vous pouvez toujours définir des symboles :


#define Pointerof(type)    type*
#define Valueof(addr)     *addr
#define Addrof(var)        &var

Retour au début


oAspect numérique

o    Conversion de types (entiers et réels)

Quand des opérandes de types différents interviennent dans une expression, elles sont toutes converties en un seul type suivant les principes suivants :
Exemples :


Resultat
Commentaire
int valeur = 3.14; 3
Le réel est tronqué. 
float valeur = 3;
3.0
L'entier est converti en réel.
float a=6;
float b = 16;
float valeur = a/10 + b/10;

2.2

a/10 vaut 0.6, b/10 vaut 1.6. La somme vaut 2.2.
float a=6;
float b = 16;
int valeur = a/10 + b/10;

2

La somme précédente est tronquée à l'entier inférieur. .

int    a = 6;
float b = 16;
float valeur = a/10 + b/10;

1.6

a/10 est tronqué à 0 (division entière), b/10 vaut 1.6 ;
int    a = 6;
float b = 16;
int valeur = a/10 + b/10;

1
La somme précédente est tronquée à l'entier inférieur le plus proche. 
int a = 6;
int b = 16;
int valeur = a/10 + b/10;

1

a/10 est tronqué à0, b/10 est tronqué à1 ; ce sont des divisions entières.


o    Particularité des réels

Le traitement spécifique des nombre flottants occupe une grande partie du cours d'Analyse numérique.

Si un dépassement de capacité ou une division par zéro provoquent une erreur fatale pour les nombres entiers, ce n'est pas le cas pour les nombres réels. Une norme (IEEE) a associé au type flottant certaines valeurs particulières (+Infini, -Infini, Indeterminé (ou Undefined ou Not a number ou NaN)).  On a défini une arithmétique spécifique qui permet de poursuivre le calcul. Par exemple Infini+infini = Infini, Infini-Infini=NaN, 1/Infini = 0, .... Charge au programmeur de vérifier si le résultat final est un nombre réel ou l'une de ces 3 pseudo valeurs.

L'addition de réels peut provoquer des troncatures. Soit y = a + b. Si a est très grand par rapport à b, la valeur de b est ignorée, et à l'arrivée y et a ont la même valeur.

Les égalités de réels se ramènent souvent à un encadrement : x <= y + epsilon et x >= y - epsilon.

Retour au début


oOrganisation des fonctions dans un logiciel

On le redit : l'appel de fonction est l'instruction la plus importante. Elle permet en une ligne d'effectuer un traitement qui pourrait éventuellement avoir été réalisé en une ou plusieurs milliers de lignes. L'appel de fonction permet (devrait permettre) de regrouper les instructions d'un logiciel en des programmes de taille raisonnable qui devraient donc rester clairs et lisibles.

Si l'on développe un logiciel au sein d'une équipe, ou si l'on souhaite réutiliser certains développements, ou tout simplement si l'on souhaite être capable de relire ce que l'on a écrit avant son départ en vacances, il convient de réfléchir à l'organisation des fonctions

Les différentes fonctions d'un logiciel sont le plus souvent regroupées par catégorie dans des fichiers séparés. Chaque composante concerne un thème particulier et agit sur des données spécifiques. On parle de programmation modulaire.

Chaque composante du logiciel agit sur une autre au moyen de fonctions. Les prototypes de ces fonctions sont contenus dans un include.  Les spécifications de ces fonctions  constituent la partie "visible" ou "publique" d'une composante. Les données internes d'une composante sont "privées" et inaccessibles aux autres composantes. On parle aussi d'encapsulation des données.

La programation dite "objet" n'est que le prolongement de la programmation modulaire. On y ajoute le concept d'"héritage", et on y regroupe fonctions et données dans des "classes".

C++ et Java permettent la mise en oeuvre des concepts objet. Dans ce cours, même si on utilise des syntaxes C++, on n'abordera pas la programmation dite "objet" Pour cela on pourra se référer à l'(excellent) ouvrage de Claude Delanoy. "Programmer en C++". (Editions Eyrolles).

Néanmoins les principes de modularité et d'encapsulation sonr réalisables dans n'importe quel langage. Y compris en Fortran 77. Le but est d'écrire des programmes plus faciles à maintenir, et de confier leur développement à des spéialistes.

Retour au début


oBibliographie
 
Ce cours n'est qu'une initiation à la programmation. Pour en savoir plus sur les "vrais" langages, je vous renvoie à des ouvrages spécialisés.

 o Le Langage C ANSI.                    Philippe Drix.       Editions Dunod.
            Philippe Drix part de Pascal et arrive à C

 o Programmer en langage C++.     Claude Delanoy     Editions Eyrolles.
           Claude Delanoy part de C et arrive à C++

 o Programmer en langage Java.     Claude Delanoy     Editions Eyrolles.
           Cette fois-ci le point de comparaison est C++

Claude Delanoy a écrit d'autres ouvrages sur le C, Fortran 90. N'hésitez pas à les consulter.


Mais le plus important n'est pas tant de connaître totalement les subtilités d'un langage. C'est même secondaire par rapport à  la connaissance des algorithmes les plus fréquents.

  o Algorithmes en langage C    Robert Sedgewick    Editions Dunod    .

Retour au début