Semaine 1 : Sémantique et révisions

Semaine 1 : Sémantique et révisions#

TP : révisions: fonctions, programmes, …#

À faire

Solutions pour l’exercice banque

Exercice 0

  1. Téléchargez le devoir «Entrainement» depuis le tableau de bord.

  2. À la maison, effectuez une dizaine d’exercices pour réviser, ou plus selon vos besoins.

Exercice 1: portée des variables (sur papier)

On considère le programme ci-dessous qui, incidemment, n’est pas un exemple de bonne programmation. Précisez la portée de chacune des variables et donnez les valeurs affichées.

int x = 10;
int main() {
    {
        int x = 5;
        cout << "1) x vaut :" << x << endl;
    }
    {
        cout << "2) x vaut :" << x << endl;
    }
    for (int x = 0; x < 3; x++) {
        cout << "3) x vaut :" << x << endl;
    }
    cout << "4) x vaut :" << x;
    {
        cout << "5) x vaut :" << x << endl;
        int x = 7;
    }
    for (x = 0; x < 3; x++) {
        cout << "6) x vaut :" << x << endl;
    }
    cout << "7) x vaut :" << x << endl;
    return 0;
}

BEGIN SOLUTION

  • En 1, on accède à la variable \(x\) du bloc courant, on affiche donc 5.

  • En 2, on est maintenant dans un deuxième bloc, la variable du bloc précédent n’est donc plus visible et l’on accède donc à la variable globale et affiche 10.

  • Ensuite trois passages dans la boucle vont être réalisés et afficher en 3 les valeurs 0, 1 et 2 en réalisant un accès à la variable locale de la boucle.

  • En 4, comme en 2, plus aucune variable locale n’est accessible et l’on accède donc à la variable globale afin d’afficher 10.

  • En 5, une variable locale \(x\) est déclarée dans le bloc mais après l’affichage, une nouvelle fois c’est donc la valeur 10 de la variable globale qui est affichée et la variable locale n’est pas utilisée.

  • En 6, l’affichage sera exactement le même que pour la première boucle mais tout se fait en utilisant la variable globale à la place de la variable locale.

  • En 7 on affiche une dernière fois la variable globale mais celle-ci vaut maintenant 3 car elle a été modifiée par la boucle.

END SOLUTION

Exercice 2: compilation, exécution, espace de travail

Vous allez maintenant exécuter le programme de l’exercice précédent afin de vérifier votre solution. Pour cela, il faudra préalablement le compiler, c’est-à-dire transformer le programme en binaire directement exécutable par l’ordinateur.

  1. Créez un fichier portee_variables.cpp et copiez y le programme de l’exercice précédent.

  2. Organisez votre espace de travail et à ajustez la taille des caractères (avec Ctrl-+ et Ctrl--) pour avoir une vue confortable simultanée de tous les éléments requis : consignes, code, terminal.

    https://nicolas.thiery.name/Enseignement/Info111/media/screencast-espace-de-travail-compilation.gif

    Fig. 1 Vidéo: organiser son espace de travail avec JupyterLab#

  3. Essayez de compiler le programme avec la commande suivante dans le terminal :

    clang++ -std=c++11 -Wall portee_variables.cpp -o portee_variables
    

    Indications

    • Veillez à créer le programme dans le dossier Semaine1.

    • Veillez à lancer la compilation depuis le même dossier Semaine1. Utilisez la commande cd si nécessaire.

    • L’option -std=c++11 demande au compilateur d’utiliser la norme ISO C++ de 2011,

    • L’option -Wall active l’affichage de tous les messages d’avertissement possibles.

    • L’option -o portee_variables permet de nommer l’exécutable produit, ici portee_variables

    La compilation doit échouer avec une erreur use of undeclared identifier 'cout'. À quelle ligne? Quelle colonne? Que signifie ce message d’erreur?

  4. Ajoutez au début du programme les deux lignes suivantes (à quoi servent-elles?) et recompilez le :

    #include <iostream>
    using namespace std;
    

    Cette fois, il ne doit pas y avoir d’erreur.

  5. Exécutez le programme compilé obtenu avec la commande suivante :

    ./portee_variables
    

Exercice 3 : nombres premiers

  1. Créez un fichier premiers.cpp et recopiez au début de celui-ci les trois lignes suivantes :

    #include <iostream>
    using namespace std;
    #define CHECK(C) if ( !(C) ) { std::cerr << "Test failed: "#C << std::endl; }
    
  2. Pour que ce fichier soit transmis lorsque vous déposerez votre devoir, il faut le signaler au gestionnaire de version git. Pour cela, ouvrez le terminal, déplacez vous si nécessaire dans le dossier ProgAsd/Semaine1 et utilisez la commande suivante :

    git add premiers.cpp
    
  3. Dans premiers.cpp, définissez une fonction estDiviseur spécifiée par la documentation suivante:

    /** test de division entière
     * @param p un entier non nul
     * @param q un entier
     * @return true si p divise q et false sinon
     */
    

    Indication

    On rappelle que, pour deux entiers a et b, l’expression a % b calcule le reste de la division entière de a par b.

    BEGIN SOLUTION

    bool estDiviseur(int p, int q) {
        return (q % p) == 0;
    }
    

    Astuce

    Les parenthèses ne sont pas indispensables dans ce cas, mais elle peuvent améliorer la lisibilité.

    END SOLUTION

  4. Ajoutez dans premiers.cpp la fonction de tests suivante :

    void estDiviseurTest() {
        CHECK(     estDiviseur(1, 15) );
        CHECK( not estDiviseur(2, 15) ) 
        CHECK(     estDiviseur(3, 15) );
        CHECK(     estDiviseur(5, 15) );
        CHECK(     estDiviseur(15, 15));
    }
    
  5. Complétez le programme premiers.cpp avec une fonction main qui lance les tests de estDiviseur. Depuis votre terminal, compilez et exécutez le programme, et mettez le au point jusqu’à ce qu’il soit correct.

  6. La cellule suivante vérifie que premiers.cpp existe, peut être compilé, et que la fonction estDiviseurTest s’exécute sans erreur :

Exercice 3 (suite)

  1. Un nombre premier est un entier naturel qui a exactement deux diviseurs: \(1\) et lui-même. Spécifiez et définissez dans premiers.cpp une fonction estPremier qui prend en argument un entier naturel et renvoie true si cet entier est un nombre premier et false sinon. Vous devez utiliser la fonction estDiviseur.

    Indication

    Dans cet exercice et les suivants, on ne cherchera pas à optimiser les algorithmes. L’objectif est de travailler les bases de la programmation et notamment les fonctions.

    BEGIN SOLUTION

    /** test de primalité
     * @param m un entier naturel
     * @return true si m est un nombre premier et false sinon
     */
    bool estPremier(int m) {
        if ( m <= 1 )
    	return false;
        for ( int d = 2; d < m; d++ )
    	if ( estDiviseur(d, m) )
    	    return false;
        return true;
    }
    

    END SOLUTION

  2. Comme précédemment, définissez une fonction de test pour estPremier, vérifiant par exemple que \(2\), \(3\), \(5\), \(7\) sont premiers mais que \(0\), \(1\), \(4\), \(6\), \(9\) ne le sont pas. Appelez cette fonction de test depuis la fonction main. Compilez et exécutez le programme, et mettez le au point jusqu’à ce qu’il soit correct.

    BEGIN SOLUTION

    void estPremierTest() {
        CHECK( estPremier(2) );
        CHECK( estPremier(3) );
        CHECK( estPremier(5) );
        CHECK( estPremier(7) );
        CHECK( not estPremier(0) );
        CHECK( not estPremier(1) );
        CHECK( not estPremier(4) );
        CHECK( not estPremier(6) );
        CHECK( not estPremier(9) );
    }
    

    END SOLUTION

Exercice 3 (suite)

  1. Définissez une fonction premiersBornes qui prend en argument un entier \(n\) et qui affiche tous les nombres premiers inférieurs à \(n\). Vous devez utiliser la fonction estPremier.

    BEGIN SOLUTION

    void premiersBornes(int n) {
        for (int k = 2; k < n; k++) {
    	if (estPremier(k)) {
    	    cout << k << " ";
    	}
        }
    }
    

    END SOLUTION

  2. Complétez la fonction main de votre programme pour qu’elle demande à l’utilisateur un entier \(n\) puis qu’elle affiche les nombres premiers inférieurs à \(n\) en faisant appel à la fonction premiersBornes. Compilez, exécutez et vérifiez votre programme. Par exemple, si l’utilisateur entre 12, les nombres affichés doivent être : 2, 3, 5, 7 et 11. Attention, 9 ne doit pas être affiché!

    Note

    Pourquoi n’a t’on pas écrit de fonction de test pour premiersBornes? Comment pourrait-on s’y prendre pour autrement pour pouvoir automatiser les tests?

    BEGIN SOLUTION

    premiersBornes est une procédure qui affiche son résultat plutôt que de le renvoyer. Pour pouvoir tester la logique interne, il faudrait découpler le calcul et l’affichage, en transformant premiersBornes pour renvoyer les nombres premiers, par exemple sous forme d’un tableau.

    END SOLUTION

  3. Définissez une fonction premiersPremiers qui prend en argument un entier \(n\) et qui affiche les \(n\) premiers nombres premiers. Vous devez utiliser la fonction estPremier.

    BEGIN SOLUTION

    void premiersPremiers(int n) {
    	int compteur = 0;
    	int nombre = 2;
    	while (compteur < n) {
    		if (estPremier(nombre)) {
    			cout << nombre << " ";
    			compteur = compteur + 1;
    		}
    		nombre = nombre + 1;
    	}
    }
    

    END SOLUTION

  4. Complétez la fonction main pour qu’elle affiche en plus les \(n\) premiers nombres premiers en faisant appel à la fonction premiersPremiers. Compilez, exécutez et vérifiez votre programme. Par exemple, si l’utilisateur saisit 5, les nombres affichés sont 2, 3, 5, 7 et 11. Si l’utilisateur entre 12, alors 12 nombres sont affichés.

Exercice 4: lecture de valeurs bornées

  1. Créez un nouveau fichier lectureVB.cpp et, comme dans l’exercice précédent, signalez le à git pour qu’il soit transmis lorsque vous déposerez votre devoir. Vous devrez par la suite répéter cette procédure pour chaque exercice.

  2. Définissez une fonction lectureVB (pour lecture Valeur Bornée) qui prend deux paramètres réels, min et max, puis demande à l’utilisateur de saisir une valeur réelle comprise entre ces deux bornes, et renvoie cette valeur. Par exemple, on peut l’utiliser pour demander de saisir un nombre d’heures en imposant qu’il soit compris entre \(0\) et \(23\).

    La fonction doit afficher un message d’erreur si la valeur saisie n’est pas comprise entre les bornes et re-demander à l’utilisateur une nouvelle saisie (en précisant quelle est l’erreur) jusqu’à pouvoir renvoyer une valeur correcte.

    BEGIN SOLUTION

    float lectureVB(float min, float max) {
        // On verifie que les bornes sont correctes sinon on inverse
        if (min > max) {
    	float temp = min;
    	min = max;
    	max = temp;
        }
        // La saisie avec une boucle do while
        float res;
        do {
    	cout << "Saisissez un entier compris entre "
    	     << min << " et " << max ;
    	cin >> res ;
    	if (res < min) {
    	    cout << "Recommencez, valeur trop petite" << endl;
    	} else if (res > max)  {
    	    cout << "Recommencez, valeur trop grande" << endl;
    	}
        } while ((res < min) or (res > max));
        return res;
    }
    

    END SOLUTION

  3. Complétez le programme avec une fonction main qui fait appel à votre fonction. Compilez, exécutez et vérifiez.

Exercice 5 : banque

Une banque fait un prêt à une personne pour un montant total de \(s_0\) euros. Cette personne rembourse chaque mois un montant fixe \(r\) et paye en plus un intérêt variable \(i = i_m \times s\)\(i_m\) est le taux d’intérêt mensuel fixe et \(s\) la somme restant à rembourser (avant déduction du remboursement mensuel).

Écrivez un programme banque.cpp qui demande à l’utilisateur le montant emprunté (\(s_0\)), le montant remboursé par mois (\(r\)) et le taux d’intérêt mensuel (\(i_m\), qui doit être compris entre \(0.1\%\) et \(1.5\%\) pour ne pas dépasser le taux d’usure annuel fixé à \(19.96\%\)), puis qui calcule et affiche la durée du remboursement et la somme des intérêts versés.

Indications et bonnes pratiques

  • Pour les lectures, vous pourrez réutiliser la fonction lectureVB quand cela vous paraîtra pertinent.

  • Veillez à découper votre programmes en fonctions de sorte, notamment, à bien séparer calculs et entrées-sorties.

  • Pour chaque fonction, vous commencerez par écrire la documentation et des tests.

  • Définir une variable s qui représente la somme due à la banque au début du mois (on appelle cette propriété un invariant), et faire évoluer au fil des mois cette variable jusqu’à ce qu’il n’y ait plus rien à rembourser.

BEGIN SOLUTION

À faire

END SOLUTION

Exercice 6 : chiffres romains

Dans un fichier romains.cpp, définissez une fonction chiffresRomains qui prend un entier strictement positif en paramètre et renvoie une chaîne de caractères contenant sa représentation en chiffres romains. Par exemple, chiffresRomains(1997) renverra MCMXCVII.

BEGIN SOLUTION

La première version est relativement simple :

string chiffresRomainsBasique(int n) {
    string résultat = "";
    while (n >= 1000) { résultat += "M";  n = n - 1000; }
    if    (n >=  500) { résultat += "D";  n = n -  500; }
    while (n >=  100) { résultat += "C";  n = n -  100; }
    if    (n >=   50) { résultat += "L";  n = n -   50; }
    while (n >=   10) { résultat += "X";  n = n -   10; }
    if    (n >=    5) { résultat += "V";  n = n -    5; }
    while (n >=    1) { résultat += "I";  n = n -    1; }
    return résultat;
}

La deuxième n’est pas beaucoup plus complexe mais demande de l’attention pour bien envisager tous les cas possibles :

string chiffresRomainsBasique(int n) {
    string résultat = "";
    while (n >= 1000) { résultat += "M";  n = n - 1000; }
    if    (n >=  500) { résultat += "D";  n = n -  500; }
    while (n >=  100) { résultat += "C";  n = n -  100; }
    if    (n >=   50) { résultat += "L";  n = n -   50; }
    while (n >=   10) { résultat += "X";  n = n -   10; }
    if    (n >=    5) { résultat += "V";  n = n -    5; }
    while (n >=    1) { résultat += "I";  n = n -    1; }
    return résultat;
}

END SOLUTION