ApprentissageProgrammationCpp  ..
Travaux pratiques

Table des matières

On vous conseille dans un premier temps de ne lire que la section Objectif de l'exercice et de réfléchir soigneusement à la réalisation du code chacun de votre côté. N'adoptez surtout pas pour aller plus vite les idées des autres, vous devez imaginer par vous même les choses à faire, vous pouvez vous tromper et poser des questions à vos enseignants, et recommencer... mais si vous codez la solution de votre voisin, vous vous mettriez dans un simple rôle d'exécutant qui vous empêchera de progressez.

Ceux qui sont vraiment bloqués, peuvent ensuite lire la section Quelques conseils pour bien débuter.

Vous pouvez tous lire la section Quelques remarques.

Exercice manipulation des types et gestion des inputs utilisateur

Exercice 1 : basique

Écrire un programme qui affiche le message suivant à l'execution

$ ./a.out
Hello World from 60 boulmich

Exercice 2 : input

Écrire un programme qui demande à l'utilisateur de saisir deux dimensions longueur et largeur et affiche en retour l'air et le périmètre du rectangle.

Par exemple :

$ ./a.out
Saisir la longueur (m) : 10.05
Saisir la largeur (m): 3.42
La surface est 34.371 m2
Le périmètre est 26.94 m

Exercice 3

Écrire un programme qui demande à l'utilisateur de saisir 5 entiers et qui en calcul la moyenne. Vous ne devez définir que deux variables int dans votre programme.

Exercice 4

Ecrire un programme qui demande à l'utilisateur de renseigner le prix HT d'un produit, le nombre de produit acheté et la TVA applicable et qui affiche en retour le montant TTC total à payer.

Variante: Refaire le même programme mais où les trois valeurs d'entrée sont fournies à la ligne de commande au lancement du programme.

Exercices structures de contrôle

Exercice 1

Écrire un programme qui demande à l'utilisateur de saisir N entiers, N étant fourni par l'utilisateur et qui affiche la somme de ces N entiers.

Exercice 2

Écrire un programme qui demande à l'utilisateur de saisir N entiers positifs, N étant fourni par l'utilisateur et qui affiche la somme de ces N entiers. Si l'utilisateur fourni un nombre négatif alors le programme doit afficher un message d'erreur et offrir la possibilité à l'utilisateur de saisir une nouvelle valeur.

Exercice 3

Écrire un programme qui demande à l'utilisateur de saisir un entier N et qui affiche la figure suivante.

N=1
*
N=2
**
*
N=3
***
**
*

Exercice 4 : suite hongroise

On considère la suite hongroise : u(0)=a (a entier) si u(n) pair alors u(n+1)=u(n)/2 sinon u(n+1)=3*u(n)+1

Pour toutes les valeurs a, il existe un entier N tel que u(N)=1.

Partie 1 Écrire un programme qui demande à l'utilisateur de taper a et qui affiche toutes les valeurs de u(n) de n=1 à n=N.

Partie 2 Écrire un programme qui demande à l'utilisateur de taper un entier M puis qui cherche la valeur de a comprise entre 2 et M qui maximise la valeur de N. On appelle A cette valeur. La programme doit afficher la valeur A et la valeur N correspondante.

TP sur la calculatrice en notation polonaise inversée

Objectif de l'exercice

Écrire un programme C++ qui évalue une expression arithmétique fournie en notation polonaise inversée en argument à votre programme (i.e. sur la ligne de commande le programme), et affiche le résultat sur la sortie standard.

En notation polonaise inversée (**R**everse **P**olish **N**otation) les opérateurs suivent leurs opérandes et il n'est donc pas nécessaire de parenthéser les sous-expressions. Par exemple, 3+4*5 devient 3 4 5 x + en polonaise inversée et 2*5+4/2 devient 2 5 x 4 2 / +

Supposons que votre exécutable s'appelle rpn_cpp.

La première expression sera évaluée comme cela:

$ ./rpn_cpp 3 4 5 x +
23

La seconde comme cela:

$ ./rpn_cpp 2 5 x 4 2 / +
12

Quelques remarques

  1. séparez bien les termes de l'expression sur la ligne de commande par des espaces NON ./rpn_cpp 3 5+ OUI ./rpn_cpp 3 5 +
  1. Attention à bien utiliser le symbole x pour la multiplication et pas * (parce que * est un caractère spécial quand il est sur la ligne de commande)
  1. Utilisez ! pour le moins unaire et pas - ainsi 2*-2 sera 2 2 ! *
  1. Comme vous ne savez pas encore gérer les exceptions, dans un premier temps vous pouvez considérer que les expressions (données en argument au programme) sont bien formées. Mais nous vous invitons à réfléchir et à repérer, dans votre code, les potentiels problèmes.

(attention spoiler) Quelques conseils pour débuter

  1. Ne programmez pas tout d'un coup mais petit à petit en veillant à ce que votre code compile à chaque étape. Par exemple, une première version, peut être un exécutable qui prend les arguments sur la ligne de commande et les affiche.
./rpn_cpp 3 5 x
3
5
x
  1. Ensuite décomposez le traitement d'une expression: Que se passe-t-il quand votre programme va rencontrer l'entrée 3 5 x ?
    • il rencontre 3 (il ne sait rien calculer, il doit le garder)
    • il rencontre 5 (il ne sait rien calculer, il doit le garder)
    • il rencontre x (il sait que c'est la multiplication binaire dont il possède déjà les deux opérandes; il sait aussi qu'il n'aura plus besoin des deux opérandes mais du résultat du calcul ici 15)
    • il n'y a plus rien après, l'expression est terminée, le résultat doit être affiché 15

Spirale d'Archimède

L'objectif de cet exercice est de développer le code permettant d'afficher la spirale d'archimède. La sortie doit se faire sous la forme d'une image au format PPM

Ensemble de Mandelbrot

L'objectif est de développer le code permettant de tracer la célèbre image de l'ensemble de mandelbrot

Liste chaînée

Objectif de l'exercice

L'objectif de cet exercice est que vous fassiez votre propre implémentation de la liste chaînée. Les fonctionnalitées attendues sont les suivantes :

Liste doublement chaînée

Objectif de l'exercice

L'objectif de cet exercice est que vous fassiez votre propre implémentation de la liste doublement chaînée. Les fonctionnalitées attendues sont les suivantes :

Evaluateur expression

Objectif de l'exercice

L'objectif de cette exercice est de mettre en place un système d'évaluation d'expression mathématique. Par exemple le code attendu pour l'évaluation de l'expression y*(10+x) est le suivant :

int main(){
std::unique_ptr<Node> left = std::make_unique<ConstantNode>(10.);
std::unique_ptr<Node> right = std::make_unique<VarNode>("x");
std::unique_ptr<Node> toto = std::make_unique<VarNode>("y");
std::unique_ptr<Node> tmp = std::make_unique<BinaryNode>("+", left, right, ope_add);
std::unique_ptr<Node> root= std::make_unique<BinaryNode>("*", toto, tmp, [](const double&a, const double&b ){return a*b;});
root->print();
std::map<std::string, double> vars = {{"x", 10.}, {"y", 10.}};
std::cout << "Value of toy example : " << root->eval( vars ) << std::endl;
return EXIT_SUCCESS;
}

Pour cela il vous faut définir une hiérarchie de classes avec les bonnes interfaces. Enjoy ;)

Calcul matriciel et multi-threading

Dans cet exercice on vous propose de mettre en place une fonction pour réaliser un produit matrice vecteur. On vous fourni le squelette de code suivant :

#include <iostream>
#include <vector>
#include <thread>
#include <random>
#include <chrono>
#include <functional>
#include <algorithm>
#include <omp.h>
// ON TOUCHE A RIEN ICI //
template<typename T, typename ...Args>
void benchmark(const std::string& name, const uint& repeat, std::function<std::vector<T>(const Matrix<T>&, const std::vector<T>& vec, Args... args)> mult, const Matrix<T>& m, const std::vector<T>& v, Args... args){
std::vector<T> out;
auto start = std::chrono::high_resolution_clock::now();
for( uint i=0; i<repeat; i++){
out = mult(m, v, args...);
}
auto stop = std::chrono::high_resolution_clock::now();
std::cout << name << " (" << repeat << " runs) :" <<std::chrono::duration_cast<std::chrono::milliseconds>(stop-start).count()/repeat << " ms" << std::endl;
}
template<typename T>
class Matrix{
unsigned int _rows;
unsigned int _cols;
std::vector<T> _data;
unsigned int idx(const uint& i, const uint& j) const {
return i*this->_cols + j;
}
public:
Matrix(const int m, const int n): _rows(m), _cols(n){
_data.resize(this->_rows*this->_cols);
}
T& operator()(const unsigned int i, const unsigned int j){
return this->_data[this->idx(i, j)];
}
T operator()(const unsigned int i, const unsigned int j) const {
return this->_data[this->idx(i, j)];
}
uint rows() const {
return this->_rows;
}
uint cols() const {
return this->_cols;
}
};
template<typename T>
std::vector<T> sequential_mult(const Matrix<T>& mat, const std::vector<T>& vec){
std::vector<T> out(mat.rows());
for( uint i=0; i<mat.rows(); i++){
out[i] = 0.;
for( uint j=0; j<mat.cols(); j++){
out[i] += mat(i,j)*vec[j];
}
}
return out;
}
template<typename T>
std::vector<T> thread_mult(const Matrix<T>& mat, const std::vector<T>& vec, const int& n_threads){
// TODO
}
template<typename T>
std::vector<T> openmp_mult(const Matrix<T>& mat, const std::vector<T>& vec, const int n_threads){
// TODO
}
int main(int argc, char* argv[]){
if( argc != 3 ){
std::cerr << "Usage : mat_vec n_row n_col" << std::endl;
return 1;
}
std::random_device rd;
std::mt19937 gen(rd());
std::uniform_real_distribution<double> dis(1.0, 2.0);
const uint n_row = atoi(argv[1]);
const uint n_col = atoi(argv[2]);
const uint repeat = 100;
Matrix<double> mat(n_row, n_col);
std::vector<double> vec(n_col);
for( uint i=0; i<n_row; i++){
for( uint j=0; j<n_col; j++){
mat(i,j) = dis(gen);
}
}
for( uint j=0; j<n_col; j++){
vec[j] = dis(gen);
}
benchmark("sequential",repeat, sequential_mult<double>, mat, vec );
benchmark("thread #2",repeat, thread_mult<double>, mat, vec, 2 );
benchmark("thread #4",repeat, thread_mult<double>, mat, vec, 4 );
benchmark("thread #8",repeat, thread_mult<double>, mat, vec, 8 );
benchmark("OpenMP #2",repeat, openmp_mult<double>, mat, vec, 2 );
benchmark("OpenMP #4",repeat, openmp_mult<double>, mat, vec, 4 );
benchmark("OpenMP #8",repeat, openmp_mult<double>, mat, vec, 8 );
return 0;
}

L'objectif est de faire la version parallèle en utilisant des std::thread ou de l'OpenMP.