TD 4 : Des fonctions, des tests et de la documentation#
Notes aux enseignants
La notion d’appel de fonction vs définition de la fonction n’est pas
simple pour les débutants. Une erreur classique est d’indiquer le type
au moment de l’appel (ex : cout << int factorielle(3);
). Faire le
parallèle avec les variables : on déclare le type d’une variable une
fois pour toutes et ensuite on l’utilise sans le rappeler (ex : cout << x;
) et c’est pareil avec les fonctions.
Une autre difficulté est de comprendre ce qu’est réellement un paramètre, et la différence entre paramètre et variable locale. Par exemple dans l’exercice 1 beaucoup d’élèves ont tendance à mettre pi en paramètre.
Exercice 1 : Premières fonctions
Voici une fonction qui calcule la surface d’un rectangle :
float surfaceRectangle(float longueur, float largeur) {
return longueur * largeur;
}
Définissez une fonction
surfaceDisque
qui calcule la surface d’un disque de rayon donné. On prendra \(\pi=3,1415926\)./// BEGIN SOLUTION
float surfaceDisque(float rayon) { return 3.1415926 * rayon * rayon; }
/// END SOLUTION
Définissez une fonction
surfaceTriangle
qui calcule la surface d’un triangle de base et de hauteur données./// BEGIN SOLUTION
float surfaceTriangle(float base, float hauteur) { return base * hauteur / 2; }
/// END SOLUTION
Exercice 2 : En route vers l’exponentielle
Nous avons vu en cours et en TP une fonction
factorielle(n)
qui calcule la factorielle d’un entier positif \(n\). Pour un exercice du TP à venir, et pour éviter les problèmes de dépassement de capacité, il est souhaitable que les calculs intermédiaires et le résultat soient desdouble
. Adaptez en conséquence la fonctionfactorielle
./// BEGIN SOLUTION
/** La fonction factorielle * @param n un nombre entier positif * @return n! comme un nombre à virgule flottante à double précision **/ double factorielle(int n) { double resultat = 1; for ( int k = 1; k <= n; k++ ) { resultat = resultat * k; } return resultat; }
/// END SOLUTION
On considère la fonction dont la documentation et l’entête sont donnés ci-dessous :
/** La fonction puissance * @param a un nombre à virgule flottante en double précision * @param n un nombre entier positif * @return la n-ième puissance a^n de a **/
Quels sont les types de ses paramètres formels et de sa valeur de retour?
/// BEGIN SOLUTION
Le paramètre formel
a
est de typedouble
, le paramètre formeln
est de typeint
, la valeur de retour est de typedouble
./// END SOLUTION
Écrivez quelques exemples d’utilisation de la fonction
puissance
. Éditez-les sous forme de tests, en vous inspirant du test suivant pour la fonctionsurfaceRectangle
:CHECK( surfaceRectangle(4, 5) == 20 );
/// BEGIN SOLUTION
CHECK( puissance(2.5, 0) == 1.0 ); CHECK( puissance( 35, 1) == 35.0 ); CHECK( puissance( -2, 4) == 16.0 ); CHECK( puissance( 10, 5) == 100000.0 );
/// END SOLUTION
Définissez la fonction
puissance
./// BEGIN SOLUTION
/** La fonction puissance * @param a un nombre à virgule flottante en double précision * @param n un nombre entier positif * @return la n-ième puissance a^n de a **/
/// END SOLUTION
Cherchez dans les notes de cours la sémantique simplifiée de l’appel d’une fonction.
Exécutez pas à pas le programme suivant :
int k = 3; double x = 6; double a = 4; double resultat = puissance(x - a, k) / factorielle(k); cout << resultat << endl;
Quelle est la valeur de la variable
resultat
à la fin?/// BEGIN SOLUTION
Les trois premières lignes initialisent les variables
k
,x
eta
. Ensuite on calculepuissance(x - a, k) / factorielle(k)
, qu’on stocke dansresultat
.Au moment du calcul, les expressions passées aux fonctions auxiliaires sont remplacées par leur valeur. Ainsi l’appel effectif est
puissance(2, 3) / factorielle(3)
.Il ne reste plus qu’à exécuter pas à pas la fonction
puissance
avec \(2\) et \(3\) comme valeurs initiales pour ses paramètres formelsa
etn
: on obtient \(8.0\) comme valeur de retour.On fait de même avec
factorielle
avec \(3\) comme valeur initiale pour le paramètre formeln
, ce qui donne \(6.0\).Il ne reste qu’à faire la division
8.0 / 6.0
, ce qui donne environ \(1.333\), et c’est la valeur deresultat
./// END SOLUTION
\(\clubsuit\) Définissez les fonctions
factorielle
etpuissance
en récursif cette fois, puis refaites l’exécution pas à pas. Qu’est-ce qui change?/// BEGIN SOLUTION
/** La fonction factorielle récursive * @param n un nombre entier positif * @return n! comme un nombre à virgule flottante à double précision **/ double factorielle(int n) { double resultat = n; if ( n == 0 ) { resultat = 1; } else { resultat = resultat * factorielle(n-1); } return resultat; }
/// END SOLUTION
/// BEGIN SOLUTION
/** La fonction puissance récursive * @param a un nombre à virgule flottante en double précision * @param n un nombre entier positif * @return la n-ième puissance a^n de a **/ double puissance(double a, int n) { if ( n == 0 ) { return 1; } else { return a * puissance(a , n - 1); } }
/// END SOLUTION
Notes aux enseignants
Les étudiants ont vu en cours la sémantique détaillée de l’appel de fonctions, mais c’est encore fragile. Dans l’exercice suivant, forcer les étudiants à suivre pas à pas les étapes décrites dans la sémantique de l’appel de fonction, en utilisant la même représentation de la pile que dans le poly. À corriger soigneusement au tableau, avec le même formalisme que dans le cours (donc pas comme la présentation compacte utilisée dans la correction ci-dessous).
À faire
Dessins de pile dans les corrections. 2024-2025?
Mettre à jour la correction pour utiliser le même type de représentation que dans le poly (voir au passage comment on peut produire des représentations de ce type en markdown).
Exercice 3 : Variables locales/globales, pile et exécution pas à pas
On considère les deux programmes suivants :
int i = 0;
int f(int j) {
i = i + j;
return i;
}
int resultat = f(1) + f(2) + f(3);
int f(int j) {
int i = 0;
i = i + j;
return i;
}
int resultat = f(1) + f(2) + f(3);
Mettez en évidence les différences entre les deux programmes (par exemple au surligneur).
Cherchez dans les notes de cours la sémantique détaillée de l’appel d’une fonction (formalisation suivant le modèle d’exécution).
Exécutez pas à pas les deux programmes en décrivant au fur et à mesure l’état de la mémoire (pile). Quelle est la valeur des variables
i
etresultat
à la fin de l’exécution?Décrivez la différence de comportement entre ces programmes, et retrouvez dans les notes de cours le commentaire à ce propos.
/// BEGIN SOLUTION
À faire
Solution exercice 3 à rédiger
/// END SOLUTION
Exercice 4 : La trilogie code, documentation, tests
Analysez la fonction volumePiscine
suivante :
/** Calcule le volume d'une piscine parallélépipédique
* @param profondeur la profondeur de la piscine (en mètres)
* @param largeur la largeur de la piscine (en mètres)
* @param longueur la longueur de la piscine (en mètres)
* @return le volume de la piscine (en litres)
**/
double volumePiscine(double profondeur, double largeur, double longueur) {
return 100 * profondeur * largeur * longueur;
}
Munie des tests :
CHECK( volumePiscine(5, 12, 5) == 30000 );
CHECK( volumePiscine(1, 1, 5) == 500 );
Est-ce que les tests passent?
/// BEGIN SOLUTION
Oui!
/// END SOLUTION
La documentation, le code et les tests sont-ils cohérents?
/// BEGIN SOLUTION
Les tests et l’implantation ne sont pas cohérents avec la documentation, car cette dernière spécifié que le résultat doit être exprimé en litres, alors que dans le test et l’implantation le résultat calculé est exprimé en décalitres.
/// END SOLUTION
Corrigez les anomalies éventuelles.
/// BEGIN SOLUTION
Le facteur de conversion devrait être de \(10^3=1000\) et pas de \(100\).
/// END SOLUTION
Exercice 5 : \(\clubsuit\)
Analysez la fonction mystere
suivante :
string mystere(int blop) {
string schtroumpf = "";
for ( int hip = 1; hip <= blop; hip++ ) {
for ( int hop = 1; hop <= hip; hop++ ) {
schtroumpf += "*";
}
schtroumpf += "\n";
}
return schtroumpf;
}
Munie des tests :
CHECK( mystere(0) == "" );
CHECK( mystere(1) == "*\n" );
CHECK( mystere(2) == "*\n**\n" );
CHECK( mystere(3) == "*\n**\n***\n" );
Comment fait-on appel à cette fonction (quelle est sa syntaxe)?
/// BEGIN SOLUTION
La signature de la fonction :
string mystere(int blop);
nous indique que la fonction attend un seul paramètre entier (
blop
). On écrit doncmystere(x)
oùx
est un entier pour appeler la fonction./// END SOLUTION
Que fait cette fonction (quelle est sa sémantique)?
Indications : pour les chaînes de caractères, l’opérateur+
représente la concaténation (par exemple"Cou" + "cou"
a pour valeur"Coucou"
); comme pour les entiers,x += expression
est un raccourci pourx = x + expression
; enfin, dans une chaîne de caractères, «\n
» représente un saut de ligne./// BEGIN SOLUTION
La fonction crée une chaîne de caractères représentant un triangle d’étoiles ayant
blop
lignes et la renvoie. La boucle externe se charge des lignes, tandis que la boucle imbriquée se charge des colonnes.Ainsi,
mystere(4)
renvoie la chaîne de caractères :* ** *** ****
/// END SOLUTION
Ré-écrivez la fonction en choisissant des noms pertinents pour la fonction et ses variables et en la faisant précéder de sa documentation.
/// BEGIN SOLUTION
/** Triangle d'étoiles * @param nbLignes un nombre entier positif * @return une chaîne de caractère représentant un triangle d'étoiles * ayant nbLignes lignes **/ string triangleEtoiles(int nbLignes) { string resultat = ""; for ( int ligne = 1; ligne <= nbLignes; ligne++ ) { for ( int etoile = 1; etoile <= ligne; etoile++ ) { resultat += "*"; } resultat += "\n"; } return resultat; }
/// END SOLUTION
Exercice 6 : \(\clubsuit\)
Le but de cet exercice est de coder une fonction point_de_chute
qui
calcule l’abscisse \(x_c\) à laquelle tombe un projectile lancé en \(x =
0\) avec une vitesse \(v\) suivant un angle \(\alpha\) (exprimé en degrés
par rapport à l’horizontale). Définissez la fonction
point_de_chute
. On commencera par écrire sa documentation ainsi que
des tests (voir TD 1).
Rappels :
l’abscisse est donnée par la formule : \(x_c=(2v_xv_y)/g\) où \(v_x = v\cos(\alpha)\), \(v_y = v\sin(\alpha)\) et \(g\) est l’accélération gravitationnelle (environ \(\SI{9,8}{\meter\per\second\squared}\) sur la planète Terre).
en C
++
, les fonctions mathématiques sinus et cosinus sont implantées par les fonctions prédéfiniessin(arg)
etcos(arg)
dans<cmath>
, où l’anglearg
est exprimé en radians.
/// BEGIN SOLUTION
/** Fonction qui calcule le point de chute d'un objet lancé
* @param v la vitesse de lancer en m/s
* @param angle l'angle de lancer en degrés, entre 0 et 180
* @return l'abscisse du point de chute comme un nombre à virgule flottante
* à double precision
**/
double pointDeChute(int v, int angle) {
double angleRadians = angle * 3.141592 / 180;
return 2 * v * cos(angleRadians) * v * sin(angleRadians) / 9.8;
}
void pointDeChuteTest() {
CHECK(pointDeChute(3, 0) == 0);
CHECK(pointDeChute(3, 90) == 0);
CHECK(pointDeChute(0, 45) == 0);
CHECK(pointDeChute(3, 30) + pointDeChute(3, 150) == 0);
}
/// END SOLUTION
Exercice 7 : \(\clubsuit\)
Le but de cet exercice est de calculer la hauteur en fonction du temps \(z(t)\) à laquelle se trouve un pot de fleur (\(m=\SI{3}{\kilogram}\)) lâché à \(t=0\) depuis le 10ème étage (\(h_0=\SI{27}{\meter}\)), en chute libre avec résistance de l’air; puis de calculer le temps de chute.
Définissez une fonction
chute_libre(t)
calculant \(z(t)\) pour un \(V_0\) donné (\(V_0=\SI{80}{\meter\per\second}\)).
Indications :La hauteur s’exprime en fonction du temps par
\[z(t)=h_0-(V_0t+\frac{V_0^2}{g} \ln\left(\frac{1}{2}\left(1+e^{-2tg/V_0}\right)\right)\,,\]où \(V_0\) est la vitesse limite de chute de l’objet et \(g=\SI{9.81}{\meter\per\second\squared}\).
La fonction logarithme népérien est prédéfinie sous la forme
log(arg)
dans<cmath>
.
/// BEGIN SOLUTION
/** Calcul de la hauteur d'un objet en chute libre * @param time: le temps de chute * @return la hauteur de l'objet au temps time **/ double chute_libre_simple(double t) { double g = 9.81; double Vo=80; double ho=27; return ho - Vo*t - Vo*Vo/g*log( (1 + exp(-2*t*g/Vo)) / 2 ); }
/// END SOLUTION
Que se passe-t-il si on varie \(h_0\) et \(V_0\)? Généralisez votre fonction pour prendre en paramètres additionnels la hauteur initiale \(h_0\) et la vitesse limite de chute \(V_0\). Pour la gravité, définir une variable globale \(g\).
Bonus : définir cette variable globale comme une constante (nous irons sur Mars une autre fois)./// BEGIN SOLUTION
double g = 9.81; /** Calcul de la hauteur d'un objet en chute libre * @param time: le temps de chute * @param h0: le hauteur au début de la chute * @param V0: la vitesse limite * @return la hauteur de l'objet au temps time **/ double chute_libre(double t, double ho, double Vo) { return ho - Vo*t - Vo*Vo/g*log( (1 + exp(-2*t*g/Vo)) / 2 ); }
/// END SOLUTION
Écrivez les appels à la fonction précédente pour calculer \(z(t)\) pour \(t=\SI{2}{s}\) et pour différentes valeurs de \(V_0\): \(10\), \(40\), \(60\), \(120\), \(\SI{180}{\meter\per\second}\).
/// BEGIN SOLUTION
chute_libre(2,27,10); chute_libre(2,27,40); chute_libre(2,27,60); chute_libre(2,27,120); chute_libre(2,27,180);
/// END SOLUTION
Écrivez une fonction
temps_de_chute
qui prend les même paramètres que précédemment et utilisechute_libre
de façon répétée pour déterminer une approximation de la durée \(t_c\) de la chute du pot de fleur jusqu’au sol./// BEGIN SOLUTION
/** Calcul d'une approximation du temps de chute * @param precision: la precision attendue, un reel strictement positif * @param h0: le hauteur au début de la chute * @param V0: la vitesse limite * @return une approximation du temps de chute **/ double temps_de_chute(double precision, double ho, double Vo) { double t1=0; double t2=1; double t3; //on commence par chercher un temps t2 qui soit plus grand que //le temps de chute, //le temps t1 doit lui rester plus petit que le temps de chute while (chute_libre(t2,ho,Vo)>0){ t1=t2; t2=2*t2; } //on cherche ensuite t par dichotomie while (t2-t1>precision){ t3=(t1+t2)/2; if (chute_libre(t3,ho,Vo)<0){ t2=t3; } else { t1=t3; } } return t1; }
/// END SOLUTION
La vitesse limite peut être obtenue en fonction de la masse volumique de l’air \(\rho\), du coefficient de résistance aérodynamique \(C_x\) et de la section de l’objet \(S\) à l’aide de la formule \(V_0=\sqrt{\frac{2mg}{C_x \rho S}}\). Définissez une fonction
vitesse_limite
pour calculer cette formule. Puis implantez de nouvelles fonctions utilisant les précédentes pour calculer \(z(t)\) et le temps de chute \(t_c\) en fonction des paramètres \(C_x\), \(S\), et \(m\). On suppose que \(\rho\) est une variable globale déjà définie./// BEGIN SOLUTION
double vitesse_limite(double Cx, double S, double m){ return sqrt(2*m*g/(Cx*rho*S)); }
/// END SOLUTION
Exercice 8 : \(\clubsuit\) Triangles rectangles à côtés entiers
Inspiré du problème 39 du Projet Euler, Integer right triangles
Pour un périmètre \(p=120\) donné, il n’existe que trois configurations pour un triangle rectangle dont les côté sont de longueurs entières : \(\{20, 48, 52\}, \{24, 45, 51\}, \{30, 40, 50\}\).
Documentez et écrivez une fonction nombreTrianglesRectanglesEntiers
qui prend en entrée un entier, le périmètre fixé, et renvoie le nombre
de configurations possibles de triangles rectangles à côtés de
longueurs entières.
Indications : On pensera à écrire des fonctions intermédiaires utiles, par exemple une fonction qui vérifie que la longueur de l’hypothénuse d’un triangle rectangle est entière et renvoie sa valeur à partir des longueurs de ses deux autres côtés, ou une fonction qui vérifie si la somme des longueurs des trois côtés vaut bien le périmètre fixé. Ne pas oublier de prévoir des tests automatiques pour chaque fonction qui le permet.
/// BEGIN SOLUTION
/** Une fonction qui calcule la longueur de l'hypothénuse d'un triangle
* rectangle à partir des longueurs de ses deux autres côtés
* @param x un entier, longueur d'un côté du triangle
* @param y un entier, longueur d'un côté du triangle
* @return la longueur de l'hypothénuse si elle est entière, -1 sinon
* */
int longueurHypothenuse(int x, int y) {
int carreHypothenuse = x*x + y*y;
int racine = 1;
while (racine * racine < carreHypothenuse) {
racine++;
}
if (racine * racine == carreHypothenuse) {
return racine;
} else {
return -1;
}
}
/** Une fonction qui calcule le paramètre d'un triangle à partir des longueurs
* de ses côtés
* @param x un entier, longueur d'un côté du triangle
* @param y un entier, longueur d'un côté du triangle
* @param z un entier, longueur d'un côté du triangle
* @return le périmètre du triangle
* */
int perimetre(int x, int y, int z) {
return x + y + z;
}
/** Une fonction qui renvoie le nombre de triangles rectangles entiers de
* périmètre donné
* @param p un entier positif, le périmètre donné
* @return le nombre de triangles rectangles de côtés entiers de périmètre p
* */
int nombreTrianglesRectanglesEntiers(int p) {
int c;
int nombre = 0;
for ( int b = 1; b <= p / 2; b++ ) {
for ( int a = 1; a <= b; a ++ ){
c = longueurHypothenuse(a, b);
if ((c > 0) && (perimetre(a, b, c) == p)){
nombre++;
}
}
}
return nombre;
}
Avec les tests :
CHECK( perimetre(1, 2, 3) == 6 );
CHECK( perimetre(15, 5, 3) == 23 );
CHECK( longueurHypothenuse(20, 48) == 52 );
CHECK( longueurHypothenuse(24, 45) == 51 );
CHECK( nombreTrianglesRectanglesEntiers(120) == 3 );
/// END SOLUTION