Choisissez votre style : colorisé, impression

Correction 6
Fonctions (2)


Les solutions suivantes ne sont évidemment pas uniques. Si vous en trouvez de meilleures (ou si vous trouvez des erreurs), faites-le savoir ! Merci.
Par ailleurs, les solutions proposées correspondent à l'état de vos connaissances au moment où la série est abordée. D'autres solutions pourront être envisagées une fois de nouveaux concepts acquis.
Exercice 1 Exercice 2 Exercice 3 Exercice 4 Exercice 5 Exercice 6

Exercice 1 : Surcharge de fonction (niveau 1)

Exercice n°13 (pages 31 et 212) de l'ouvrage C++ par la pratique.

En C++, une fonction est identifiée par son nom et par les types de ses paramètres. On peut en effet imaginer que certaines fonctions se comportent différemment selon le type de paramètre transmis : on n'inverse par exemple pas une matrice de la même manière que l'on inverse un nombre réel.

Dans cet exercice par contre, le contenu de la fonction n'est pas modifié car on échange toujours 2 objets de la même façon. Ce qui change par contre ici, c'est le type des paramètres et de la variable intermédiaire.

void echange(double& a, double& b)
{
  double copie(a);
  a=b;
  b=copie;
}
 
void echange(char& a, char& b)
{
  char copie(a);
  a=b;
  b=copie;
}
[avancé !] Notez que le langage C++ offre une possibilité de faire ce genre de surcharges plus facilement : définir des fonctions indépendamment du type de leurs arguments. Ce sont les templates, que nous verrons au second semestre.

C'est un peu comme si on écrivait la fonction générique :

void echange(<type>& a, <type>& b)
{
  <type> copie(a);
  a = b;
  b = copie;
}

justement sans avoir à préciser le type.

La façon correct de l'écrire en C++ est :

template<typename Type>
void echange(Type& a, Type& b)
{
  Type copie(a);
  a = b;
  b = copie;
}
 
[avancé !] [C+++11] Notez par ailleurs que depuis la version 2011, le langage C++ offre une possibilité de faire cela de façon plus efficace, en évitant les copies lorsque cela est approprié, en utilisant la «sémantique de déplacement» («move semantics»). Ceci est une optimisation avancée ; à n'utiliser que si vous savez exactement ce que vous faites :
template<typename Type>
void echange(Type& a, Type& b)
{
  Type tmp(std::move(a)); // Cela rend a invalide (mais peu importe ici puisqu'on l'écrase à la ligne suivante)
  a = std::move(b);       // Cela rend b invalide (mais peu importe ici puisqu'on l'écrase à la ligne suivante)
  b = std::move(tmp);     // Cela rend tmp invalide (mais peu importe ici puisqu'on ne l'utilise plus)
}

Exercice 2 : factorielle

Exercice n°32 (pages 81 et 251) de l'ouvrage C++ par la pratique.
  1. Ouvrez le fichier factorielle.cc dans votre éditeur favori et saisissez les lignes classiques pour la base du programme :
    #include <iostream>
    using namespace std;
     
    int main()
    {
      return 0;
    }
     
    
  2. Pour la fonction factorielleIterative, nous devons tout d'abord la prototyper :
    #include <iostream>
    using namespace std;
     
    unsigned int factorielleIterative(unsigned int n);
     
    int main()
    {
      return 0;
    }
    
    puis nous pouvons ensuite la définir (par exemple à la fin du fichier). Je vous conseille de toujours commenter vos programmes, et en particulier de dire ce que fait chaque fonction, quels sont ses arguments et à quoi correspond la valeur de retour.

    La définition (c.-à-d. le corps) de la fonction factorielleIterative ne présente aucune difficulté. Il s'agit d'une simple boucle for. Il faut simplement veiller aux bonnes conditions initiales.

    Voici donc l'état de votre programme à ce stade :

    #include <iostream>
    using namespace std;
     
    unsigned int factorielleIterative(unsigned int n);
     
    int main()
    {
        return 0;
    }
     
    /* ----------------------------------------------------------------------
     * Calcule de façon itérative la factorielle d'un nombre entier positif.
     * Entrée : le nombre n dont on veut calculer la factorielle
     * Sortie : n!
     * ---------------------------------------------------------------------- */
    unsigned int factorielleIterative(unsigned int n)
    {
      unsigned int fact(1);
      for (unsigned int i(2); i <= n; ++i) {
        fact = fact * i;
      }
      return fact;
    }
    
  3. De même nous prototypons puis définissons la fonction factorielleRecursive :
    #include <iostream>
    using namespace std;
     
    unsigned int factorielleIterative(unsigned int n);
    unsigned int factorielleRecursive(unsigned int n);
     
    int main()
    {
      return 0;
    }
     
    /* ----------------------------------------------------------------------
     * Calcule de façon itérative la factorielle d'un nombre entier positif.
     * Entrée : le nombre n dont on veut calculer la factorielle
     * Sortie : n!
     * ---------------------------------------------------------------------- */
    unsigned int factorielleIterative(unsigned int n)
    {
      unsigned int fact(1);
      for (unsigned int i(2); i <= n; i++) {
        fact *= i;
      }  
      return fact;
    }
     
    /* ----------------------------------------------------------------------
     * Calcule de façon récursive la factorielle d'un nombre entier positif.
     * Entrée : le nombre n dont on veut calculer la factorielle
     * Sortie : n!
     * ---------------------------------------------------------------------- */
    unsigned int factorielleRecursive(unsigned int n)
    {
      if (n == 0) {
        return 1;
      }
      else {
        return (n * factorielleRecursive(n-1));
      }
    }
     
    
  4. Cette fonction récursive est construite sur le schéma vu en cours :

    1. Détermination de la condition d'arrêt :
      if (n == 0) return 1;
      On pourrait même optimiser un peu plus et écrire  :
      if (n <= 1) return 1;
    2. Écriture de la relation de récurrence :
      n * factorielleRecursive(n-1)
  5. L'ajout de la fonction demander_nombre se fait exactement comme déjà vu les semaines passées.
  6. Il faut maintenant écrire le corps principal du programme (fonction main).

    La base est assez simple :

    1. Déclarer une variable entière :
      unsigned int n;
    2. Demander sa valeur. Tant qu'à faire pourquoi ne pas le faire lors de l'initialisation de cette variable ? :
      unsigned int n(demander_nombre(0, 12));
    3. Appeler successivement les deux méthodes et afficher les résultats :
      cout << "Méthode itérative :" << endl;
      cout << "    " << n << "! = " << factorielleIterative(n) << endl;
      cout << "Méthode récursive :" << endl;
      cout << "    " << n << "! = " << factorielleRecursive(n) << endl;
            
  7. La boucle demandant à l'utilisateur s'il souhaite recommencer se fait exactement comme dans l'exercice 0.

On a donc finalement le programme suivant :

#include <iostream>
#include <limits>
#include <string>
using namespace std;
 
unsigned int demander_nombre(unsigned int min, unsigned int max);
unsigned int factorielleIterative(unsigned int n);
unsigned int factorielleRecursive(unsigned int n);
 
// ----------------------------------------------------------------------
int main()
{
  char rep;
 
  do {
    unsigned int n(demander_nombre(0,12));
    cout << "Méthode itérative :" << endl;
    cout << "    " << n << "! = " << factorielleIterative(n)
         << endl;
    cout << "Méthode récursive :" << endl;
    cout << "    " << n << "! = " << factorielleRecursive(n)
         << endl;
 
    do {
      cout << "Voulez-vous recommencer [o/n] ? ";
      cin >> rep;
    } while ((rep != 'o') and (rep != 'n'));
  } while (rep == 'o');
 
  return 0;
}
 
/* ----------------------------------------------------------------------
 * Calcule de façon itérative la factorielle d'un nombre unsigned int positif.
 * Entrée : le nombre n dont on veut calculer la factorielle
 * Sortie : n!
 * ---------------------------------------------------------------------- */
unsigned int factorielleIterative(unsigned int n)
{
  unsigned int fact(1);
  for (unsigned int i(2); i <= n; ++i) {
    fact *= i;
  }
  return fact;
}
 
/* ----------------------------------------------------------------------
 * Calcule de façon récursive la factorielle d'un nombre unsigned int positif.
 * Entrée : le nombre n dont on veut calculer la factorielle
 * Sortie : n!
 * ---------------------------------------------------------------------- */
unsigned int factorielleRecursive(unsigned int n)
{
  if (n == 0) {
    return 1;
  }
  else {
    return (n * factorielleRecursive(n-1));
  }
}
 
/* --------------------------------------------------------------
 * fonction demandant à l'utilisateur un nombre compris
 * dans un intervalle [a, b], ou supérieur ou égal à a
 * si b < a.
 * Entrée : les deux nombres a et b définissant l'intervalle
 * Sortie : le nombre saisi par l'utilisateur
 * -------------------------------------------------------------- */
unsigned int demander_nombre(unsigned int a, unsigned int b)
{
    unsigned int res;
 
    do {
      cout << "Entrez un nombre entier ";
      if (a >= b) {
        cout << "supérieur ou égal à " << a;
      }
      else {
        cout << "compris entre " << a << " et " << b;
      }
      cout << " : ";
      cin >> res;
    
    } while ((res < a) or ((a < b) and (res > b)));
 
   return res;
}
 

Exercice 3 : nombres de Fibonacci

Exercice n°33 (pages 82 et 254) de l'ouvrage C++ par la pratique.

Cet exercice est très similaire au précédent et se fait de la même façon.

Le programme complet est :

#include <iostream>
#include <limits>
#include <string>
using namespace std;
 
int demander_nombre(int min, int max);
int Fibonacci(int n);
int FibonacciIteratif(int n);
 
// ----------------------------------------------------------------------
int main()
{
  char rep;
 
  do {
    int n(demander_nombre(0, 40));
    cout << "Méthode itérative :" << endl;
    cout << "    F(" << n << ") = " << FibonacciIteratif(n)
         << endl;
    cout << "Méthode récursive :" << endl;
    cout << "    F(" << n << ") = " << Fibonacci(n) << endl;
 
    do {
      cout << "Voulez-vous recommencer [o/n] ? ";
      cin >> rep;
    } while ((rep != 'o') and (rep != 'n'));
  } while (rep == 'o');
 
  return 0;
}
 
/* ----------------------------------------------------------------------
 * Calcule de façon itérative le n-ieme nombre de Fibonacci.
 * Entrée : le nombre n 
 * Sortie : F(n)
 * ---------------------------------------------------------------------- */
int FibonacciIteratif(int n)
{
  int Fn(0);    // stocke F(i)  , initialisé par F(0)
  int Fn_1(Fn); // stocke F(i-1), initialisé par F(0)
  int Fn_2(1);  // stocke F(i-2), initialisé par F(-1)
 
  for (int i(1); i <= n; ++i) {
    Fn   = Fn_1 + Fn_2;    // pour n>=1 on calcule F(n)=F(n-1)+F(n-2)
    Fn_2 = Fn_1;           // et on decale...
    Fn_1 = Fn;
  }
  return Fn;
}
 
/* ----------------------------------------------------------------------
 * Calcule de façon récursive le n-ieme nombre de Fibonacci.
 * Entrée : le nombre n 
 * Sortie : F(n)
 * ---------------------------------------------------------------------- */
unsigned int Fibonacci(unsigned int n)
{
  if (n == 0) 
    return 0;
  else if (n == 1)
    return 1;
  else
    return Fibonacci(n-1) + Fibonacci(n-2);
}
 
/* --------------------------------------------------------------
 * fonction demandant à l'utilisateur un nombre compris
 * dans un intervalle [a, b], ou supérieur ou égal à a
 * si b < a.
 * Entrée : les deux nombres a et b définissant l'intervalle
 * Sortie : le nombre saisi par l'utilisateur
 * -------------------------------------------------------------- */
int demander_nombre(int a, int b)
{
    int res;
 
    do {
      cout << "Entrez un nombre int ";
      if (a >= b) {
         cout << "supérieur ou égal à " << a;
      }
      else {
         cout << "compris entre " << a << " et " << b;
      }
      cout << " : ";
      cin >> res;
    } while ((res < a) or ((a < b) and (res > b)));
 
   return res;
}
 

Exercice 4 : Calcul approché d'une intégrale (niveau 2)

Exercice n°15 (pages 33 et 214) de l'ouvrage C++ par la pratique.
#include <iostream>
#include <cmath>
using namespace std;
 
double f(double x);
double integre(double a, double b);
double demander_nombre();
 
int main()
{
  double a(demander_nombre());
  double b(demander_nombre());
 
  // on définit la précision de l'affichage à 12 chiffres après la virgule
  cout.precision(12);
 
  cout << "Integrale de f(x) entre " << a
       << " et " << b << " :" << endl;
  cout << integre(a,b) << endl;
  return 0;
}
 
double f(double x) { return x*x; }
// double f(double x) { return x*x*x ; }
// double f(double x) { return 1.0/x ; }
// double f(double x) { return sin(x); }
 
double integre(double a, double b)
{
  double res;
  res =  41.0 * ( f(a) + f(b) )
      + 216.0 * ( f((5*a+b)/6.0) + f((5*b+a)/6.0) )
      +  27.0 * ( f((2*a+b)/3.0) + f((2*b+a)/3.0) )
      + 272.0 * f((a+b)/2.0) ;
  res *= (b-a)/840.0;
  return res;
}
 
double demander_nombre()
{
  double res;
  cout << "Entrez un nombre réel : ";
  cin >> res;
  return res;
}
 

Exercice 5 : Fonctions logiques

On peut remarquer que A ET A = A. NON(A ET A) vaut donc NON(A). On peut alors écrire la fonction non ainsi :

bool non(bool A)
{
  return non_et(A, A);
}
 

Comme NON(NON(C)) = C, on a NON(NON(A ET B)) = A ET B. On peut donc écrire la fonction et ainsi:

bool et(bool A, bool B)
{
  return non(non_et(A, B));
}
 

NON(A OU B) = NON(A) ET NON(B). Donc, (A OU B) = NON(NON(A OU B)) = NON(NON(A) ET NON(B)). On peut donc écrire la fonction ou ainsi:

bool ou(bool A, bool B)
{
  return non_et(non(A), non(B))
}

Ces relations sont intéressantes en électronique: il suffit d'un seul type de composant (effectuant la fonction non_et) pour réaliser n'importe quel circuit logique.


Exercice 6 : recherche dichotomique

Exercice n°34 (pages 83 et 256) de l'ouvrage C++ par la pratique.

Aucune difficulté majeure pour cet exercice qui est niveau 2 uniquement parce que l'énoncé est moins détaillé.

Voici le code correspondant :

#include <iostream>
#include <limits>
using namespace std;
 
int demander_nombre(int min, int max);
unsigned int cherche(unsigned int borneInf, unsigned int borneSup);
 
constexpr unsigned int MIN(1);
constexpr unsigned int MAX(100);
 
// ----------------------------------------------------------------------
int main()
{
  cout << "Pensez à un nombre entre " << MIN << " et " << MAX << "."
       << endl;
  cout << "Tapez sur 'Entrée' quand vous êtes prêt(e)." << endl;
  // l'instruction suivante permet de faire une lecture en ignorant le contenu
  // lu (nous aurons l'occasion d'y revenir)
  cin.ignore(numeric_limits<streamsize>::max(), '\n');
  
  const unsigned int solution( cherche(MIN, MAX) );
  cout << endl << "Votre nombre était " << solution << '.' 
       << endl;
  return 0;
}
 
/* ----------------------------------------------------------------------
 * Recherche d'un nombre par dichotomie dans un intervalle [a b]
 * Entrée : les bornes de l'intervalle
 * Sortie : la solution
 * ---------------------------------------------------------------------- */
unsigned int cherche(unsigned int a, unsigned int b)
{
  //  cout << "[ " << a << ", " << b << " ]" << endl;
 
  if (b < a) {
    cerr << "ERREUR: vous avez répondu de façon inconsistante !" << endl;
    return b;
  }
 
  unsigned int pivot((a+b)/2);
  char rep;
 
  do {
    cout << "Le nombre est il <, > ou = à " << pivot << " ? ";
    cin >> rep;
  } while ((rep != '=') and (rep != '<') and (rep != '>'));
 
  switch (rep) {
  case '=':
    return pivot;
  case '<':
    return cherche(a, pivot-1);
  case '>':
    return cherche(pivot+1, b);
  }
}
 

Le seul petit truc auquel il faut penser est peut être le switch : oui ! on peut faire un switch sur un caractère.


Dernière mise à jour : 2025/10/14 (16:36)