Introduction au C++ : La page des TDs

Il y a 4 parties indépendantes, la difficulté est censée être croissante. Les durées approximatives sont indiquées. Vous pouvez ne pas faire tous les exercices d'une partie, et passer à la partie suivante.

1 - Mises en bouche (~30min)

Hello World

Créer un fichier exos.cpp contenant le code suivant.


   #include<iostream>

   int main(){
	return 0;
   }

Compiler et exécuter ce programme.

   toto@saturne ~ $ g++ exos.cpp -o exos
   toto@saturne ~ $ ./exos

Faire en sorte que ce programme affiche Hello World!.

Multiplication

Coder une fonction qui calcule le produit de deux entiers, avec les opérateurs ==, !=, + et -. La tester.

   int multiplication(int a,int b);

Swap

Coder une fonction qui intervertit les valeurs de deux variables entières, passées en argument par des pointeurs. La tester.

   void swap(int* a,int* b);

Longueur de chaîne

Coder une fonction qui calcule la longueur d'une chaîne de caractères. La tester. Une chaîne de caractères se termine toujours par le caractère '\0'.

   int longueur(char* str);

Concaténation de deux chaînes

Coder une fonction qui calcule et retourne la concaténation de deux chaînes de caractères passées en arguments. La tester.

   char* concat(char* str1, char* str2);

2 - Structures de contrôle (~1h30)

Afin de se familiariser avec les principales structures de contrôle, nous allons coder 3 algorithmes de tri. Les liens sur les pages wikipedia vont permettront de bien comprendre les algorithmes. Les pages contiennent aussi directement du code. Jouez le jeu, pas de copier/coller!

Tri par sélection

Coder une fonction qui trie un tableau d'entiers selon l'algorithme du tri par sélection.

   void tri_selection(int* tab, int n);

Tri à bulles

Coder une fonction qui trie un tableau d'entiers selon l'algorithme du tri à bulles.

   void tri_bulles(int* tab, int n);

Tri fusion

Coder une fonction qui trie un tableau d'entiers selon l'algorithme du tri fusion.

   void tri_fusion(int* tab, int n);

Vous aurez besoin d'une fonction qui interclasse deux tableaux triés. Ici, tab est un tableau d'entiers, les valeurs entre les indices deb et mil sont triées, les valeurs entre les indices (mil+1) et end sont triées. La fonction doit remplacer in situ les valeurs dans tab entre les indices deb et fin, de sorte qu'elles soient triées.

   void fusion(int* tab, int deb, int mil, int fin);

Tests de vos fonctions

Ajouter l'inclusion suivante à votre fichier.

   #include <stdlib.h>

Tester vos fonctions de tri avec le main() suivant.

int main(){

  //srand(time(NULL)); 
  int n=20;
  int* tab=new int[n];

  std::cout<<"SELECTION"<<std::endl;

  for(int i=0;i<n;i++) tab[i]=rand()%10000;

  for(int i=0;i<n;i++) std::cout<<tab[i]<<" ";
  std::cout<<std::endl;

  std::cout<<"tri"<<std::endl;
  tri_selection(tab,n);

  for(int i=0;i<n;i++) std::cout<<tab[i]<<" ";
  std::cout<<std::endl;

  std::cout<<"BULLES"<<std::endl;

  for(int i=0;i<n;i++) tab[i]=rand()%10000;

  for(int i=0;i<n;i++) std::cout<<tab[i]<<" ";
  std::cout<<std::endl;

  std::cout<<"tri"<<std::endl;
  tri_bulles(tab,n);

  for(int i=0;i<n;i++) std::cout<<tab[i]<<" ";
  std::cout<<std::endl;

  std::cout<<"FUSION"<<std::endl;

  for(int i=0;i<n;i++) tab[i]=rand()%10000;

  for(int i=0;i<n;i++) std::cout<<tab[i]<<" ";
  std::cout<<std::endl;

  std::cout<<"tri"<<std::endl;
  tri_fusion(tab,n);

  for(int i=0;i<n;i++) std::cout<<tab[i]<<" ";
  std::cout<<std::endl;

  return 0;
}

3 - Classes (~2h)

Pages d'un livre

Définir une classe book munie d'un nombre de pages et d'un numéro de page courante.

Ajouter à cette classe :

  • un constructeur,
  • un destructeur,
  • une méthode qui change le numéro de la page courante,
  •   void set_current_page(int pagenum);
    
  • une méthode pour obtenir le numéro de la page courante,
  •   int get_current_page();
    
  • une méthode qui calcule le nombre de pages encore à lire.
  •   int pages_to_finish();
    

    Écrire un programme main() qui imprime le numéro de la page courante et le nombre de pages qui restant à lire d'un livre de 200 pages.

    Translation d'un point

    Définir une classe point en 3d qui contient :

  • un constructeur à 3 coordonnées,
  • une méthode pour translater un point,
  •   void translate(int dx, dy int, int dz);
    
  • une méthode pour obtenir chacune des coordonnées.
  • Tester les méthodes en utilisant un programme main().

    Aire et périmètre d'un rectangle

    Définir une classe rectangle avec les champs et un constructeur appropriés.

    Ajouter à cette classe :

  • une méthode pour calculer l'aire,
  •   float compute_area();
    
  • une méthode pour calculer le périmètre.
  •   float compute_perimeter();
    

    Tester les méthodes en utilisant un programme main().

    Somme, produit, moyenne sur un ensemble de nombres

    Définir une classe numbers qui contient un ensemble de nombres, ainsi que :

  • une méthode pour en calculer la somme,
  • une méthode pour en calculer le produit,
  • une méthode pour en calculer la moyenne arithmétique,
  • une méthode pour en calculer la variance,
  • une méthode pour en calculer l'écart-type.
  • Tester les méthodes en utilisant un programme main().

    Aire et périmètre d'un quadrilatère

    Définir une classe quadri modélisant un quadrilatère défini par la longueur de ses quatre côtés. Définir un constructeur et une méthode pour calculer son périmètre. Définir une classe rectangle et une classe square, dérivées de quadri, contenant chacune une méthode pour calculer l'aire. Écrire un programme main() et tester vos classes.

    4 - Héritage et polymorphisme (~2h)

    Nous allons créer quatre classes qui permettent de stocker des collections ordonnées d'entiers:

  • une classe contener,
  • une classe array, dérivant de contener,
  • une classe list, dérivant de contener,
  • une classe stack, dérivant de array.
  • Nous utiliserons ici un fichier d'entêtes contener.h contenant les déclarations, et un fichier contener.cpp contenant les définitions.

    Classe contener

    La classe contener est une classe abstraite, c'est-à-dire qu'il ne sera pas possible de créer un objet de type contener.

    Écrire une classe contener contenant deux champs protected de type entier:

  • un champ nb_el : le nombre d'éléments dans la collection,
  • un champ nb_max : le nombre d'éléments maximal que peut contenir la collection.
  • Ajouter à la classe contener les méthodes suivantes:

  • un constructeur à un argument de type entier permettant d'initialiser la taille maximale,
  • une méthode size_max() retournant la taille maximale,
  • une méthode size() retournant la taille courante,
  • une méthode is_empty() indiquant si l'objet est vide,
  • une méthode is_full() indiquant si l'objet est plein.
  • Nous allons également munir la classe contener de plusieurs méthodes virtuelles pures. Elles sont donc juste déclarées dans la classe contener, elles devront être définies dans les classes qui dériveront de cette classe.

    Ajouter les déclarations suivantes:

    	virtual ~contener();
    
    	virtual void display(std::ostream& str)=0;
    
    	virtual void add(int i)=0;
    	virtual void remove(int i)=0;
    	virtual bool contains(int i)=0;
    

    En fait, le destructeur ~contener() est virtuel, pas virtuel pur.

    Classe array

    Nous allons désormais créer une classe array qui dérive publiquement de la classe contener. Dans le fichier contener.h, ajouter à la suite de la classe contener la déclaration de la classe array. Doter cette classe d'un champ tab de type int*. Celui-ci contiendra les éléments du tableau.

    Déclarer et définir le constructeur et le destructeur.

    	array(int s_max, int s, int init);
    	~array();
    

    Déclarer et définir l'opérateur []. Ce dernier permettra d'accéder au ième élément d'un objet a de type array via la syntaxe a[i]. Attention, nous voulons que les instructions suivantes puissent fonctionner : int x=a[4] et a[2]=3. Pour cela, il faut que l'opérateur renvoie une référence sur le ième élément.

    	int& operator[](int i);
    

    Enfin, déclarer et définir les fonctions display, add, remove et contains. C'est même obligatoire puisque la classe dérive de la classe abstraite contener, dans laquelle ces fonctions ont été déclarées comme virtuelles pures.

    	void display(std::ostream& str);
    	void add(int i);
    	void remove(int i);
    	bool contains(int i);
    

    Classe list

    Nous allons maintenant créer une classe list qui dérive publiquement de la classe contener. Dans le fichier contener.h, ajouter à la suite de la classe array la déclaration de la classe list.

    Nous allons avoir besoin d'une classe node pour définir chacun des maillons de la liste chaînée. Ajouter, dans la déclaration de la class list, la déclaration de la classe node. Cette classe aura deux champs publics:

  • un champ val de type int,
  • un champ succ de type node*.
  • Munir également la classe node d'un constructeur à deux arguments permettant d'initialiser ces deux champs.

    	class node{
    	  public:
    		int val;
    		node* succ;
    	        node(int v, node* s);
    	};
    
    

    Nous sommes désormais en mesure de compléter la classe list. Cette dernière sera composée d'un champ privé head de type node*, qui pointera sur le premier maillon de la liste chaînée. Déclarer ce champ.

    Déclarer et définir le constructeur et le destructeur.

    	list(int s_max);
    	~list();
    

    Enfin, comme pour la class array, déclarer et définir les fonctions display, add, remove et contains.

    	void display(std::ostream& str);
    	void add(int i);
    	void remove(int i);
    	bool contains(int i);
    

    Classe stack

    En dernier lieu, nous allons définir une classe stack, qui dérivera de la classe array. Cette classe permettra de coder la structure de données abstraite de pile. Cette fois-ci, nous allons réaliser un héritage de type private entre stack et array. Cela va permettre d'interdire à l'utilisateur de la classe stack l'accès aux méthodes publiques héritées de la classe array, alors que les définitions des fonctions de la classe stack (tout l'intérieur de la classe en fait) auront accès à ces méthodes. Ainsi, on peut contrôler précsisément l'interface de notre classe.

    class stack: private array{
    
    };
    

    Les éléments de la pile seront stockés dans le champs tab hérité de la classe array. Par contre, il faut doter la classe de méthodes publiques, qui définiront l'interface accessible à l'utilisateur de la classe. En plus d'un constructeur, déclarer et définir les méthodes classiques pour manipuler une pile.

      stack(int max);
      void push(int x);
      int pop();
      bool is_empty();
      bool is_full();
      void display(std::ostream& str);
    

    Tests

    Nous allons tout d'abord définir l'opérateur << avec comme opérande de droite un objet de type contener, afin de pouvoir utiliser la syntaxe std::cout<<x;, où x est un objet de type dérivé de contener. Il faut faire de même pour la classe stack. Ajouter les déclarations suivantes à la suite des déclarations de classes dans le fichier contener.h.

    std::ostream& operator<<(std::ostream& f, const contener& c);
    
    std::ostream& operator<<(std::ostream& f, const stack& s);
    

    Et les définitions suivantes dans le fichier contener.cpp.

    std::ostream& operator<<(std::ostream& f, const contener& c){
      c.display(f);
      return f;
    }
    
    std::ostream& operator<<(std::ostream& f, const stack& s){
      s.display(f);
      return f;
    }
    

    Ajouter désormais la fonction main() suivante. Tester là.

    int main(){
      array a(100,10,7);
      std::cout<<a<<std::endl;
      a.add(10);
      std::cout<<a<<std::endl;
      a.add(11);
      std::cout<<a<<std::endl;
      a.add(123);
      std::cout<<a<<std::endl;
      a.remove(13);
      std::cout<<a<<std::endl;
      a.remove(11);
      std::cout<<a<<std::endl;
      a[2]=11110;
      a[10]=456;
      std::cout<<a<<std::endl;
      std::cout<<a[4]<<std::endl;
    
      list l(100);
      l.add(5);
      l.add(1);
      l.add(7);
      l.add(3);
      l.add(8);
      l.add(5);
      l.add(2);
      std::cout<<l<<std::endl;
      l.remove(4);
      std::cout<<l<<std::endl;
      l.remove(8);
      std::cout<<l<<std::endl;
      l.remove(5);
      std::cout<<l<<std::endl;
      l.remove(5);
      std::cout<<l<<std::endl;
      l.remove(2);
      std::cout<<l<<std::endl;
    
      stack s(10);
      s.push(4);
      std::cout<<s<<std::endl;
      s.push(7);
      std::cout<<s<<std::endl;
      std::cout<<s.pop()<<std::endl;
      std::cout<<s<<std::endl;
      return 0;
    }