IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
Penser en Java 2nde édition - Sommaire |  Préface |  Avant-propos | Chapitre : 1  2  3  4  5  6  7  8  9  10  11  12  13  14  15 |  Annexe : A B C D  | Tables des matières - Thinking in Java

  Chapitre 3 - Contrôle du flux du programme

pages : 1 2 3 4 5 6 

Notons dans la définition précédente que chaque instruction case se termine par une instruction break, qui provoque un saut à la fin du corps de l'instruction switch. Ceci est la manière habituelle de construire une instruction switch. Cependant break est optionnel. S'il n'est pas là, le code associé à l'instruction case suivante est exécuté, et ainsi de suite jusqu'à la rencontre d'une instruction break. Bien qu'on n'ait généralement pas besoin d'utiliser cette possibilité, elle peut se montrer très utile pour un programmeur expérimenté. La dernière instruction, qui suit default, n'a pas besoin de break car en fait l'exécution continue juste à l'endroit où une instruction break l'aurait amenée de toute façon. Il n'y a cependant aucun problème à terminer l'instruction default par une instruction break si on pense que le style de programmation est une question importante.

L'instruction switch est une manière propre d'implémenter une sélection multiple (c'est à dire sélectionner un certain nombre de possibilités d'exécution), mais elle requiert une expression de sélection dont le résultat soit entier, comme int ou char. Par exemple on ne peut pas utiliser une chaîne de caractères ou un flottant comme sélecteur dans une instruction switch. Pour les types non entiers, il faut mettre en oeuvre une série d'instructions if.

Voici un exemple qui crée des lettres aléatoirement, et détermine s'il s'agit d'une voyelle ou d'une consonne :

//: c03:VowelsAndConsonants.java
// Démonstration de l'instruction switch.

public class VowelsAndConsonants {
  public static void main(String[] args) {
    for(int i = 0; i < 100; i++) {
      char c = (char)(Math.random() * 26 + 'a');
      System.out.print(c + ": ");
      switch(c) {
      case 'a':
      case 'e':
      case 'i':
      case 'o':
      case 'u':
                System.out.println("vowel");
                break;
      case 'y':
      case 'w':
                System.out.println(
                  "Sometimes a vowel");
                break;
      default:
                System.out.println("consonant");
      }
    }
  }
} ///:~

Math.random( ) générant une valeur entre 0 et 1, il faut la multiplier par la borne supérieure de l'intervalle des nombres qu'on veut produire (26 pour les lettres de l'alphabet) puis ajouter un décalage correspondant à la borne inférieure.

Bien qu'il semble que la sélection s'opère ici sur un type caractère, l'instruction switch utilise en réalité la valeur entière du caractère. Les caractères entre guillemets simples des instructions case sont également interprétés comme des entiers avant la comparaison.

Remarquez la manière d'empiler les instructions case pour affecter un même code d'exécution à plusieurs cas d'égalité. Il faut également faire attention à terminer chaque instruction par une instruction break, faute de quoi le contrôle serait transféré à l'instruction correspondant au case suivant.

Détails de calcul :

L'instruction :

char c = (char)(Math.random() * 26 + 'a');

mérite une attention particulière. Math.random( ) a pour résultat un double, par suite la valeur 26 est convertie en double avant que la multiplication ne soit effectuée ; cette dernière a aussi pour résultat un double. Ce qui signifie que 'a' doit lui aussi être converti en double avant d'exécuter l'addition. Le résultat double est finalement transtypé en char.

Que fait exactement ce transtypage ? En d'autres termes, si on obtient une valeur de 29.7 et qu'on la transtype en char, le résultat est-il 30 ou 29 ? La réponse est dans l'exemple suivant :

//: c03:CastingNumbers.java
// Qu'arrive-t-il lorsqu'on transtype un flottant
// ou un double vers une valeur entière ?

public class CastingNumbers {
  public static void main(String[] args) {
    double
      above = 0.7,
      below = 0.4;
    System.out.println("above: " + above);
    System.out.println("below: " + below);
    System.out.println(
      "(int)above: " + (int)above);
    System.out.println(
      "(int)below: " + (int)below);
    System.out.println(
      "(char)('a' + above): " +
      (char)('a' + above));
    System.out.println(
      "(char)('a' + below): " +
      (char)('a' + below));
  }
} ///:~

Le résultat :

above: 0.7
below: 0.4
(int)above: 0
(int)below: 0
(char)('a' + above): a
(char)('a' + below): a

La réponse est donc : le transtypage d'un type float ou double vers une valeur entière entraîne une troncature dans tous les cas.

Une seconde question à propos de Math.random( ) : cette méthode produit des nombres entre zéro et un, mais : les valeurs 0 et 1 sont-elles inclues ou exclues ? En jargon mathématique, obtient-on ]0,1[, [0,1], ]0,1] ou [0,1[ ? (les crochets « tournés vers le nombre » signifient « ce nombre est inclus », et ceux tournés vers l'extérieur « ce nombre est exclu »). À nouveau, un programme de test devrait nous donner la réponse :

//: c03:RandomBounds.java
// Math.random() produit-il les valeurs 0.0 et 1.0 ?

public class RandomBounds {
  static void usage() {
    System.out.println("Usage: \n\t" +
      "RandomBounds lower\n\t" +
      "RandomBounds upper");
    System.exit(1);
  }
  public static void main(String[] args) {
    if(args.length != 1) usage();
    if(args[0].equals("lower")) {
      while(Math.random() != 0.0)
        ; // Essayer encore
      System.out.println("Produced 0.0!");
    }
    else if(args[0].equals("upper")) {
      while(Math.random() != 1.0)
        ; // Essayer encore
      System.out.println("Produced 1.0!");
    }
    else
      usage();
  }
} ///:~

Pour lancer le programme, frapper en ligne de commande :

java RandomBounds lower

ou bien :

java RandomBounds upper

Dans les deux cas nous sommes obligés d'arrêter le programme manuellement, et il semble donc que Math.random( ) ne produise jamais les valeurs 0.0 ou 1.0. Une telle expérience est décevante. Si vous remarquez [26] qu'il existe environ 262 fractions différentes en double-précision entre 0 et 1, alors la probabilité d'atteindre expérimentalement l'une ou l'autre des valeurs pourrait dépasser la vie d'un ordinateur, voire celle de l'expérimentateur. Cela ne permet pas de montrer que 0.0 est inclus dans les résultats de Math.random( ). En réalité, en jargon mathématique, le résultat est [0,1[.

Résumé

Ce chapitre termine l'étude des fonctionnalités fondamentales qu'on retrouve dans la plupart des langages de programmation : calcul, priorité des opérateurs, transtypage, sélection et itération. Vous êtes désormais prêts à aborder le monde de la programmation orientée objet. Le prochain chapitre traite de la question importante de l'initialisation et du nettoyage des objets, et le suivant du concept essentiel consistant à cacher l'implémentation.

Exercices

Les solutions des exercices sélectionnés sont dans le document électronique The Thinking in Java Annotated Solution Guide, disponible pour un faible coût à www.BruceEckel.com.

  1. Dans la section « priorité » au début de ce chapitre, il y a deux expressions. Utiliser ces expressions dans un programme qui montre qu'elles produisent des résultats différents.
  2. Utiliser les méthodes ternary( ) et alternative( ) dans un programme qui fonctionne.
  3. À partir des sections « if-else » et « return », utiliser les méthodes test( ) et test2( ) dans un programme qui fonctionne.
  4. Écrire un programme qui imprime les valeurs de un à 100.
  5. Modifier l'exercice 4 de manière que le programme termine avec la valeur 47, en utilisant le mot clef break. Même exercice en utilisant return.
  6. Écrire une fonction prenant pour arguments deux String, utiliser les comparaisons booléennes pour comparer les deux chaînes et imprimer le résultat. En plus de == et !=, tester aussi equals( ). Dans la méthode main( ), appeler la fonction avec différents objets de type String.
  7. Écrire un programme qui génère aléatoirement 25 valeurs entières. Pour chaque valeur, utiliser une instruction if-then-else pour la classer (plus grande, plus petite, ou égale) par rapport à une deuxième valeur générée aléatoirement.
  8. Modifier l'exercice 7 en englobant le code dans une boucle while infinie. Il devrait alors fonctionner tant qu'on ne l'interrompt pas au moyen du clavier (classiquement avec Ctrl-C).
  9. Écrire un programme utilisant deux boucles for imbriquées ainsi que l'opérateur modulo (%) pour détecter et imprimer les nombres premiers (les nombres entiers qui ne sont divisibles que par eux-mêmes et l'unité).
  10. Écrire une instruction switch qui imprime un message pour chaque case, la mettre dans une boucle for qui teste chaque case. Mettre un break après chaque case, tester, puis enlever les break et voir ce qui se passe..

[25] John Kirkham écrivait, « J'ai fait mes débuts en informatique en 1962 avec FORTRAN II sur un IBM 1620. À cette époque, pendant les années 60 et jusqu'au début des années 70, FORTRAN était un langage entièrement écrit en majuscules. Sans doute parce que beaucoup de périphériques d'entrée anciens étaient de vieux téléscripteurs utilisant le code Baudot à cinq moments (à cinq bits), sans possibilité de minuscules. La lettre 'E' dans la notation scientifique était en majuscule et ne pouvait jamais être confondue avec la base 'e' des logarithmes naturels, toujours écrite en minuscule. 'E' signifiait simplement puissance, et, naturellement, puissance de la base de numération habituellement utilisée c'est à dire puissance de 10. À cette époque également, l'octal était largement utilisé par les programmeurs. Bien que je ne l'aie jamais vu utiliser, si j'avais vu un nombre octal en notation scientifique j'aurais pensé qu'il était en base 8. Mon plus ancien souvenir d'une notation scientifique utilisant un 'e' minuscule remonte à la fin des années 70 et je trouvais immédiatement cela déroutant. Le problème apparut lorsqu'on introduisit les minuscules en FORTRAN, et non à ses débuts. En réalité il existait des fonctions qui manipulaient la base des logarithmes naturels, et elles étaient toutes en majuscules. »

[26] Chuck Allison écrivait : Le nombre total de nombres dans un système à virgule flottante est 2(M-m+1)b^(p-1) + 1b est la base (généralement 2), p la précision (le nombre de chiffres dans la mantisse), M le plus grand exposant, et m le plus petit. Dans la norme IEEE 754, on a : M = 1023, m = -1022, p = 53, b = 2 d'où le nombre total de nombres est 2(1023+1022+1)2^52 = 2((2^10-1) + (2^10-1))2^52 = (2^10-1)2^54 = 2^64 - 2^54 La moitié de ces nombres (correspondant à un exposant dans l'intervalle[-1022, 0]) sont inférieurs à un en valeur absolue, et donc 1/4 de cette expression, soit 2^62 - 2^52 + 1 (approximativement 2^62) est dans l'intervalle [0,1[. Voir mon article à http://www.freshsources.com/1995006a.htm (suite de l'article).

Ce livre a été écrit par Bruce Eckel ( télécharger la version anglaise : Thinking in java )
Ce chapitre a été traduit par Jean-Pierre Vidal ( groupe de traduction )
télécharger la version francaise (PDF) | Commandez le livre en version anglaise (amazon) | télécharger la version anglaise
pages : 1 2 3 4 5 6 
Penser en Java 2nde édition - Sommaire |  Préface |  Avant-propos | Chapitre : 1  2  3  4  5  6  7  8  9  10  11  12  13  14  15 |  Annexe : A B C D  | Tables des matières - Thinking in Java