Créé en 2020 et modifié le 25 Août 2020

– support   ??- support  tinycar easyline

Présentation

L’algorithme

Un algorithme est une suite d’instructions

La traduction d’un algorithme en un langage de programmation s’appelle un programme.
Les mêmes données d’entrée fourniront toujours le même résultat

Le programme

Les langages, appelés langages de haut niveau, ont une grammaire et une syntaxe proches du programmeur.
L’ordinateur ne connaît que le langage machine.

Certains langages utilisent un compilateur pour transformer le code en une suite d’instruction compréhensible par l’ordinateur.

 

« Le but n’est pas que le programme fonctionne, mais qu’il fonctionne vite et bien. Le meilleur ordinateur au monde et le meilleur langage au monde ne vous y aideront pas » S.Rohaut – Algorithmique

Certains langages ne sont pas compilés mais interprétés. Pour fonctionner ces programmes nécessitent un interpréteur qui est un autre programme. Il analyse la syntaxe et l’exécute au fur et à mesure.

D’autres utilisent une machine virtuelle. La machine virtuelle est un programme qui permet d’isoler l’application de l’OS. Le programme conçu pour la machine virtuelle pourra donc fonctionner sur n’importe quel ordinateur.

Le langage C++

Créé en 1979 par Bjarne Stroustrup chez Bell . Le langage le plus utilisé en industrie et dans les jeux vidéo. Utilisé en priorité la où il faut exploiter l’architecture matérielle des processeurs de manière efficace.

 

Avantages

  • Populaire
  • Portabilité, indépendant de l’OS
  • Langage orienté objet
  • Langage de haut niveau
  • Compilé et multi tâches
  • adapté pour des exécutions rapides

Inconvénients

  • interfaces web plus difficile à mettre en place

 

 

Les variables

Une variable contient une valeur qui peut changer. Chaque variable doit avoir un nom (identifiant) et un type.

Les types de données

Le type d’une donnée détermine les valeurs qu’elle peut prendre. Les principaux types sont int, char, double, bool.

Les booléens

Ils permettent de réaliser des tests. Un booléen ne peut prendre que deux valeurs : soit true (vrai) ou false (faux)

La déclaration et l’affectation des variables

#include <iostream>
using namespace std;

int main()
{
   int a=4;
    return 0;
}

Le code suivant n’indique pas les bonnes valeurs, pourquoi ?

#include <iostream>

using namespace std;

int main()
{
    cout << "Hello World!" << endl;

    int ia; double fa; fa=3.14; ia=3.14;
    cout <<fa<< endl;
    cout <<ia<< endl;
    return 0;
}

Les chaines de caractère

Il faut inclure la bibliothèque string. On évitera l’utilisation de char[] qui ajoute un caractère en fin de chaîne.

#include <iostream>
#include <string>
using namespace std;

int main() {
  string greeting = "Hello";
  cout << greeting;
  return 0;
}

 

Saisir

#include <iostream>
using namespace std;

int main() {
  int x;
  cout << "Votre age ?";
  cin >> x; 
  cout << "Votre age :" << x;
  return 0;
}

Écrire un programme qui initialise deux variables entières et qui affiche directement la somme de ces deux nombres. N’oubliez pas d’afficher un message à l’utilisateur pour qu’il sache quoi saisir.

Compilation

Pour compiler vos fichiers C++ sous Windows, installer mingw puis ajouter C:\mingw\bin en variables d’environnement du système.

Ensuite en console, dans le répertoire contenant le fichier cpp, on utilise

g++ main.cpp -o main

Pour plusieurs fichiers, il faut les compiler dans l’ordre et générer l’exécutable ou utiliser makefile. Pour le moment, on utilisera un IDE.

 

Les structures conditionnelles

La conditionnelle

#include <iostream>
using namespace std;

int main() {
  int x = 4;
  int y = 2;
  if (x > y) {
    cout << "x est plus grand que y";
  }  
  return 0;
}

Le booléen est la condition de l’instruction si. C’est généralement une expression booléenne.

Les instructions composant le si peuvent elles-mêmes être des si :

if( qte>100){
        if( tauxTVA==0.196){
        prixTTC=prixTTC*0.9;
      }
 }

Indiquer si un nombre est supérieur à 30.

L’alternative

#include <iostream>
using namespace std;

int main() {
  int day = 7;
  switch (day) {
  case 1:
    cout << "Monday";
    break;
  case 2:
    cout << "Tuesday";
    break;
  case 3:
    cout << "Wednesday";
    break;
  case 4:
    cout << "Thursday";
    break;
  case 5:
    cout << "Friday";
    break;
  case 6:
    cout << "Saturday";
   break;
  case 7:
    cout << "Sunday";
    break;
  default :
    cout << "n'existe pas";
  }
  return 0;
}

La branche default (facultative) permet de regrouper toutes les valeurs qui n’ont pas été énumérées.

Une agence de voyage propose des réductions sur les billets d’avion de 50€ selon la catégorie de chacun de ses clients. Si les personnes sont dans la catégorie senior, ils ont 50% de réductions. Dans la catégorie nourrisson, le prix du billet est forfaitaire, il sera de 20 euros. Pour les enfants, le prix sera de 30% de celui d’origine.
Écrire le programme qui affiche le prix final pour un client.

 

Les boucles

While

La boucle de type « Tant que » permet la répétition d’un bloc d’instruction tant que la condition testée est vérifiée.

#include <iostream>
using namespace std;

int main() {
  int i = 0;
  while (i < 5) {
    cout << i << "\n";
    i++;
  }
  return 0;
}

 

Do While

Attention : on boucle quand le test est vrai, on s’arrête quand il est faux.

#include <iostream>
using namespace std;

int main() {
  int i = 0;
  do {
    cout << i << "\n";
    i++;
  }
  while (i < 5);
  return 0;
}

Écrire un programme qui saisit deux entiers et qui incrémente de 1 le premier tant qu’il n’est pas supérieur au deuxième.

For

On utilise for lorsque le nombre d’itérations est connu.

#include <iostream>
using namespace std;

int main() {
  for (int i = 0; i < 5; i++) {
    cout << i << "\n";
  }
  return 0;
}

Afficher les nombres pairs de 0 à 20 dans l’ordre décroissant

Que fait cette boucle ?

        for (int j = 0; j <= 10; j++) {
            cout<< 2 * j<< endl;
        }  
  • Le pas (incrément) de la boucle est donc un entier quelconque, mais il est fixe.
  • Un pas de zéro n’est pas une bonne idée : la boucle ne s’arrêtera jamais.
  • Le pour étant une instruction comme une autre, le corps d’un pour peut très bien contenir un autre pour (ou toute autre boucle).

Afficher pour chaque nombre de 1 à 10 les nombres qui lui précédent .

Parcours de collection

La boucle pour chaque permet de faire défiler une collection que nous verrons dans les chapitres suivants.

#include <iostream>
using namespace std;

int main()
{
    cout << "Hello World!" << endl;

    double ar[] = {1.2, 3.0, 0.8};
    int sum = 0;
    for (double d : ar) { sum += d; }
    cout << sum << endl;
    return 0;
}

 

Voici la même boucle avec la structure for classique

#include <iostream>

using namespace std;

int main()
{
    cout << "Hello World!" << endl;

   //declaration d’un tableau
    double ar[] = {1.2, 3.0, 0.8};
    int sum = 0;
    for (int i=0;i<3;i++) { sum += ar[i]; }
    cout << sum << endl;
    return 0;
}

 

  1. Écrire un programme qui saisit un entier positif.
  2. Écrire un programme qui affiche n fois ‘coucou’, l’entier n sera saisi par l’utilisateur.
  3. Écrire un programme qui saisit un nombre tant qu’il n’est pas égal à 10.
  4. Écrire un programme qui saisit un nombre tant qu’il n’est pas égal à 10 et affiche un message du style ‘ce nombre n’est pas bon’.

Les pointeurs

& permet d’obtenir l’adresse mémoire.
* indique que nous utilisons un pointeur, il va enregistrer l’adresse mémoire et non la valeur. Utiliser string* a plutôt que string *a.

#include <iostream>
#include <string>
using namespace std;

int main() {
  string food = "Pizza";  // A string variable
  string* ptr = &food;  // A pointer variable that stores the address of food

  // Output the value of food
  cout << food << "\n";

  // Output the memory address of food
  cout << &food << "\n";

  // Output the memory address of food with the pointer
  cout << ptr << "\n";

 // Value
  cout << *ptr << "\n";
  return 0;
}

 

Les fonctions

Lorsqu’un problème programme devient complexe, on le décompose en plusieurs sous-problèmes (sous-programmes).

Intérêts

– Réutilisation de la même procédure plusieurs fois au cours d’un programme en faisant un simple appel.
– Résoudre un programme long et complexe en plusieurs procédures plus simples.
– Obtenir des procédures totalement indépendantes du programme principal.

Une fonction est une suite d’instructions qui permet d’obtenir un résultat

void afficherA(){ cout<<"a"; }
int main()
{
    afficherA();
}

Valeur retournée

int renvoyer10(){ int i=10; return i; } 
int main() { cout<<renvoyer10(); }

Paramètres, valeurs par défaut

void myFunction(string fname="Jo", double b=0) {
  cout << fname << " Refsnes\n";
}

int main() {
  myFunction("Liam");
  myFunction("Jenny",5);
  myFunction("Anja");
  myFunction();
  return 0;
}

Passage par référence

Le passage par référence permet de ne pas copier le paramètre mais d’obtenir l’adresse mémoire et donc le modifier.

#include <iostream>
using namespace std;

void swapNums(int &x, int &y) {
  int z = x;
  x = y;
  y = z;
}

int main() {
  int firstNum = 10;
  int secondNum = 20;

  cout << "Before swap: " << "\n";
  cout << firstNum << secondNum << "\n";

  swapNums(firstNum, secondNum);

  cout << "After swap: " << "\n";
  cout << firstNum << secondNum << "\n";

  return 0;
}

Surcharge

Les fonctions peuvent avoir le même nom avec d’autres paramètres. Attention, le type de retourne ne définit pas deux fonctions différentes.

float myFunction(float x)
double myFunction(double x, double y)

Portée des variables

Si les instructions constituant la procédure utilisent des variables (indice de boucle par exemple), ce sont des variables locales à la procédure c’est-à-dire qui ne sont connues, et qui ne peuvent donc être utilisées que dans la fonction. Ils sont détruit à la sortie. Les variables du programme principal sont dites variables globales au programme. Elles sont lisibles par l’ensemble du programme contrairement aux variables locales.

int i = 3;

void main(){
  int i = 12;
  ::i = i + ::i; //accès à la variable globale par ::i
}

 

  1. Écrire une fonction qui affiche cinq fois bonjour
  2. Écrire une fonction qui renvoie bonjour
  3. Écrire une fonction qui renvoie le carré de 23
  4. Écrire une fonction qui renvoie la somme des nombres de 8 à 20
  5. Écrire la fonction puissance qui renvoie un nombre x à la puissance y (x et y sont connus dans le programme principal).
  6. Écrire une fonction qui prend en paramètre deux chaines de caractère et renvoie la concaténation de ces chaines
  7. Écrire une fonction Multiplie qui prend en paramètre deux réels. Ainsi Multiplie(2,4) calculera 2*4

 

La Programmation objet

La classe

Une classe est définie par son nom, ses propriétés et ses fonctions. Les objets sont des instances de classe. Ils sont structurés par les propriétés définissant la classe. Ils peuvent exécuter les fonctions définies au sein de la classe.

[enlever la méthode toString et salaire]

Le nom de la classe commence toujours par une majuscule.
Les propriétés sont : nom, annee_naissance et salaire.
Dans une classes, les fonctions s’appellent des méthodes. Ici, les méthodes sont affiche() et calcul_age().
Par convention, le nom d’une classe doit être le même que le nom du fichier.

Exemple :

Soit p1 et p2 deux instances de la classe Personne, donc deux objets Personne.
P1 a pour nom Henri, pour anne_naissance 1980 et p2 a pour nom Duval et pour annee_naissance 1982. Le salaire du premier est de 1500 et du deuxième 2100.
La fonction toString() permet à p1 d’afficher son propre contenu (idem pour p2). La fonction calcul_age() d’afficher leur propre âge.

 

On implémente les déclarations de propriétés et de méthodes dans un fichier personne.h

personne.h

#ifndef PERSONNE_H
#define PERSONNE_H
#include <string>
using namespace std;

class Personne
{
public :
    string nom;
    int anneN;
public:
  Personne( string nom="", int anneN=2000);
public:
  void calcul_age();
};
#endif // PERSONNE_H

personne.cpp

#include "personne.h"
#include <string>
#include <iostream>
#include <ctime>
using namespace std;

Personne::Personne( string nom, int dateN):nom(nom),anneN(dateN)
{
}
void Personne::calcul_age() {
    time_t t = time(0);   // get time now
    tm* now = localtime(&t);
    cout << (now->tm_year + 1900-this->anneN) << endl;
}


int main() {
  Personne p1;
  p1.calcul_age();
  p1.nom = "Jo";
  cout << p1.nom << "\n"<< p1.salaire;
  return 0;
}

Modifier le programme précédent pour qu’il puisse créer deux autres objets p2 et p3.
Réaliser une classe Sandwich qui possède comme propriété trois ingrédients, un poids et un prix.

Le constructeur

La plupart des classes doivent être dotées d’une fonction particulière appelée constructeur. Cette fonction doit porter le nom de la classe. Elle doit être déclarée en public. Elle prend en paramètre les variables que l’on souhaite initialiser.

personne.h

public:
  Personne( string nom="", int anneN=2000);


personne.cpp

Personne::Personne( string nom, int dateN):nom(nom),anneN(dateN)
{
}

Réaliser le constructeur de la classe Sandwich .
Créer un objet correspondant à la classe Sandwich en utilisant le constructeur .

Réaliser une classe logement qui possède un nombre de pièce, une surface, une adresse, un prix.
Créer deux objets correspondant à la classe.

L’encapsulation

Encapsulation avec private et public

Pour que chaque objet soit responsable de son état interne, il faut protéger les objets des interventions extérieures. C’est ce qu’on désigne par l’encapsulation.
Ce sont les valeurs des modificateurs qui fixent le niveau de protection des données et des méthodes.
Le modificateur private devant la définition des propriétés ou des méthodes interdit à d’utiliser directement la propriété ou la méthode en dehors de la classe.
Le modificateur public les rend utilisable par une autre classe.
Les éléments public sont visibles à l’extérieur, les éléments private sont réservés à un usage interne aux objets de la classe.

 

Les méthodes d’instance qui permettent d’accéder aux données privés sont appelées des accesseurs. Il existe aussi des méthodes d’instances qui permettent de modifier ces objets, ce sont des modificateurs.
En programmation objet, l’usage est d’utiliser get pour les accesseurs et set pour les modificateurs.

#ifndef PERSONNE_H
#define PERSONNE_H
#include <string>
using namespace std;

class Personne
{
private :
    string nom;
    int anneN;
public:
  Personne( string nom="", int anneN=2000);
public:
  void calcul_age();
  string getNom();
  int getAnneN();
  void setNom(string nom);
  void setAnneN(int annee);
};
#endif // PERSONNE_H



#include "personne.h"
#include <string>
#include <iostream>
#include <ctime>
using namespace std;

Personne::Personne(string nom, int dateN):nom(nom),anneN(dateN)
{
}
void Personne::calcul_age() {
    time_t t = time(0);   // get time now
    tm* now = localtime(&t);
    cout << (now->tm_year + 1900-this->anneN) << endl;
}

string Personne::getNom(){return this->nom;}
int Personne::getAnneN(){return this->anneN;};

void Personne::setNom(string nom){this->nom=nom;}
void Personne::setAnneN(int annee){this->anneN=annee;}

int main() {
  Personne p;
  p.calcul_age();
  p.setNom("Jo") ;
  cout << p.getNom() << "\n";
  return 0;
}

Réaliser une classe Etudiant qui possède un nom, un prénom, un numéro, une date de naissance et une adresse. Ces éléments sont protégés. On réalisera les accesseurs et les modificateurs nécessaires.
Créer 3 étudiants et modifier le nom du premier, afficher la date de naissance du deuxième, modifier le numéro du troisième tout en affichant son nom.

Implémenter une association en programmation objet

[enlever toString, achatVoiture et vente dans voiture et salaires]

On doit définir une variable possede de type Voiture dans la classe Personne. Comme une personne possède ou non une voiture, c’est elle qui va acheter ou vendre son véhicule. La classe Personne implémente deux méthodes, la méthode achat_voiture(v) et la methode vente_voiture(p)

 

#ifndef VOITURE_H
#define VOITURE_H
#include <string>
using namespace std;

class Voiture
{
private:
    string marque;
    string modele;
    string numero;
public:
    Voiture(string m="rien", string mod="rien", string num="rien");
    friend ostream& operator<<(ostream&, const Voiture&);
    string getMarque();

};

#endif // VOITURE_H
#include "voiture.h"

Voiture::Voiture(string m, string mod, string num):marque(m),modele(mod),numero(num)
{
}
ostream& operator<<(ostream &strm, const Voiture &v) {
  return strm <<  v.modele ;
}
string Voiture::getMarque(){
    return this->marque;
}

 

#ifndef PERSONNE_H
#define PERSONNE_H
#include <string>
#include "voiture.h"
using namespace std;

class Personne
{
private :
    string nom;
    int anneN;


public:
   Voiture possede;
  //Appeler automatiquement lors de l'affichage d'un objet
  friend std::ostream& operator<<(std::ostream&, const Personne&);

  Personne( string nom="Harry", int anneN=2000);
  void calcul_age();

  string getNom();
  int getAnneN();
  void setNom(string nom);
  void setAnneN(int annee);

  void achatVoiture(Voiture v);
  void venteVoiture(Personne& p);

};
#endif // PERSONNE_H
#include "personne.h"
#include <string>
#include <iostream>
#include <ctime>
using namespace std;

Personne::Personne( string nom, int dateN):nom(nom),anneN(dateN)
{
}
void Personne::calcul_age() {
    time_t t = time(0);   // get time now
    tm* now = localtime(&t);
    cout << (now->tm_year + 1900-this->anneN) << endl;
}
//Appeler automatiquement lors de l'affichage d'un objet Personne
ostream& operator<<(ostream &strm, const Personne &p) {
    strm <<  p.nom ;
        strm << p.possede;
    return strm;
}

string Personne::getNom(){return this->nom;}
int Personne::getAnneN(){return this->anneN;};
void Personne::setNom(string nom){this->nom=nom;}
void Personne::setAnneN(int annee){this->anneN=annee;}

void Personne::achatVoiture(Voiture v){
    this->possede=v;
}
void Personne::venteVoiture(Personne& p){
    p.possede=possede;
    Voiture v;
    possede=v;
}

int main() {
  Personne p;
  p.calcul_age();
  p.setNom("Jo") ;
 // cout << p->getNom() << "\n";
 // cout << *p<<endl;


  Voiture v;
  v.modele="5008";
  p.achatVoiture(v);
  cout<<"p1"<<p<<endl;
  Personne p2;
  cout<<"p2"<<p2<<endl;
  p.venteVoiture(p2);
  cout<<"p1"<<p<<endl;
  cout<<"p2"<<p2<<endl;
  return 0;
}

 

 

Version avec POINTEUR

Tout objet créé dynamiquement, c’est-à-dire avec le mot-clé new devra être détruit à la fin de son utilisation grâce au mot clé delete. Les objets créés de façon statique n’ont pas besoin d’être détruits, ils sont automatiquement supprimés lorsque le programme ne fonctionne plus dans la portée dans laquelle ils ont été définis. Utilisation plus rare, à modifier avec des smart pointeur.

#ifndef PERSONNE_H
#define PERSONNE_H
#include <string>
#include "voiture.h"
using namespace std;

class Personne
{
private :
    string nom;
    int anneN;
public:
   Voiture* possede=nullptr;
  //Appeler automatiquement lors de l'affichage d'un objet
  friend std::ostream& operator<<(std::ostream&, const Personne&);

  Personne( string nom="Harry", int anneN=2000);
  void calcul_age();

  string getNom();
  int getAnneN();
  void setNom(string nom);
  void setAnneN(int annee);

  void achatVoiture(Voiture* v);
  void venteVoiture(Personne* p);

};
#endif // PERSONNE_H
#include "personne.h"
#include <string>
#include <iostream>
#include <ctime>
using namespace std;

Personne::Personne( string nom, int dateN):nom(nom),anneN(dateN)
{
}
void Personne::calcul_age() {
    time_t t = time(0);   // get time now
    tm* now = localtime(&t);
    cout << (now->tm_year + 1900-this->anneN) << endl;
}
//Appeler automatiquement lors de l'affichage d'un objet Personne
ostream& operator<<(ostream &strm, const Personne &p) {
    strm <<  p.nom ;
    if(p.possede!=nullptr)
        strm << *p.possede;
    return strm;
}
string Personne::getNom(){return this->nom;}
int Personne::getAnneN(){return this->anneN;};

void Personne::setNom(string nom){this->nom=nom;}
void Personne::setAnneN(int annee){this->anneN=annee;}

void Personne::achatVoiture(Voiture* v){
    this->possede=v;
}
void Personne::venteVoiture(Personne* p){
    p->possede=this->possede;
    this->possede=nullptr;
}

int main() {
  Personne* p=new Personne();
  p->calcul_age();
  p->setNom("Jo") ;
 // cout << p->getNom() << "\n";
 // cout << *p<<endl;


  Voiture* v= new Voiture("peugeot","5008","5674965");
  p->achatVoiture(v);
  cout<<"p1"<<*p<<endl;
  Personne* p2=new Personne();
  cout<<"p2"<<*p2<<endl;
  p->venteVoiture(p2);
  cout<<"p1"<<*p<<endl;
  cout<<"p2"<<*p2<<endl;
 
 delete p1;
 delete p2;
 delete v; 

  return 0;
}

 

Créer une classe Propriétaire. Cette classe possède comme propriété un nom, et un logement.
Créer deux objets correspondant à la classe.
Le premier achète un logement t1. Il le vend au propriétaire p2.
Ecrire le code correspondant.

 

Les structures de données

Les tableaux à une dimension

On souhaite enregistrer le nom de chaque étudiant de la classe. Une solution est de créer une variable par étudiant.

string nom1 = "Jo";
string nom2 = "Daril";
string nom3 = "Dave";
string nom4 = "Harry";
string nom5 = "Doris";
...

Pour éviter d’avoir un grand nombre de variable, on peut regrouper celle-ci en une seule, un tableau. On le représente souvent comme une suite de cases contenant chacune une valeur.
On appelle indice le numéro d’une case du tableau. En programmation, la première case commence à l’indice 0.

Déclaration d’un tableau

string noms[4] = {"Jo", "Harry", "Zaza", "Tabata"};

Affectation d’un tableau

tab[0]=5;
tab[1]=4 ;
tab[2]= tab[0]+ tab[1];

Pour le compilateur, le tableau n’est qu’une suite de variables. Il ne sait pas ce qu’elles contiennent.

  1. Écrire une fonction demandant à l’utilisateur 10 entiers. Les sauvegarder dans un tableau.
  2. Écrire  une fonction qui affiche les valeurs du tableau.
  3. Écrire  une fonction qui affiche les valeurs du tableau du dernier indice au premier.
  4. Écrire  une fonction qui affiche uniquement les valeurs négatives du tableau.

 

Tableaux à deux dimensions

Quelle variable utiliser pour représenter les notes des étudiants ?  Nous allons utiliser un tableau qui contiendra les 8 notes.

int tbNotes[8] ;

La classe compte 18 étudiants . Comment faire pour gérer toutes les notes de tous les étudiants de la classe ? Une solution serait de déclarer 18 tableaux de notes (un par étudiant).

int tbNotes1[8] ;
int tbNotes2[8] ;
...

En regroupant les variables dans un tableau. Et bien, on va faire pareil que pour les noms, en regroupant les tableaux dans un tableau. Cela donne un tableau de tableaux, soit un tableau à deux dimensions.

int tbNotesEtudiants[18][8];

Il faut bien comprendre que cela correspond au tableau suivant :

  1. Déclarer un tableau qui permette d’enregistrer 2 notes pour 3 étudiants.
  2. Réaliser une fonction qui permette de demander à l’utilisateur ces notes pour les trois étudiants grâce à une boucle.
  3. Afficher la moyenne des notes pour l’ensemble des étudiants.
  4. Afficher la moyenne des notes pour l’étudiant 3.

 

Les collections Vector et Array

Les collections fonctionnent comme des tableaux. Vector permet d’avoir une taille dynamique contrairement à Array. L’intérêt d’utiliser std::array plutot qu’un tableau standard est de bénéficier de méthodes de manipulation existantes.

#include <vector>  
#include <string>  
#include <array> 
std::vector<std::string> data;
data.push_back("waza");
cout << data.size();
data.erase(data.begin()+2);

std::array<std::string, 10> data;
data[i] = "waza";

L’affichage de tous les éléments d’une collection fonctionne sur le même principe qu’un tableau .

  1. On souhaite sauvegarder le nom et le prénom des étudiants. Réaliser une classe Etudiant
  2. Réaliser une collection qui permette de sauvegarder les 15 étudiants de la classe.
  3. Ajouter les 15 étudiants.
  4. Afficher le nom de tous les étudiants de la classe.
  5. Supprimer le 4eme étudiant.

Les conventions de nommage

Les classes

  • 1ère lettre en majuscule
  • première lettre de chaque mot en majuscule
  • Eviter les acronymes : hormis ceux commun (XML, URL, HTML, …)

Variables

  • 1ère lettre en minuscule
  • première lettre de chaque mot en majuscule

Constants

  • Tout en majuscule
  • Séparer les mots par underscore ‘_’

Le mot clef static

Une fonction membre d’une classe déclarée static a la particularité de pouvoir être appelée sans devoir instancier la classe.
Elle ne peut utiliser que des variables et des fonctions static elles aussi, c’est-à-dire qui ont une existence en dehors de toute instance.

class Personne
{ 
public:    
    String nom; 
    void setNom(string nom) {}; 

    static int compteur; 
    static void f() {}; 
}; 
  
// IMPORTANT : il faut définir la variable static 
int Personne::var = 0; 
  
int main() 
{ 
    Personne p;   
    p.nom = "zaza"; 
    p.setNom("zaza"); 
  
    Personne::var = 1; 
    Personne::f(); 
}

 

Héritage et Polymorphisme

On peut hériter une classe d’une classe mère. La classe fille pourra utiliser les propriétés et méthodes de la classe mère comme ses propres propriétés. L’héritage permet de construire des hiérarchies de classe.

#ifndef CLIENT_H
#define CLIENT_H
#include "personne.h"

class Client : public Personne
{
private :
    string tel;
public:
    Client( string tel);
    Client( string nom, string tel);
public:
    void setTel(string tel);
    string getTel();
};

#endif // CLIENT_H

 

#include "client.h"

Client::Client(string tel):tel(tel){}
Client::Client(string nom,string tel): Personne(nom),tel(tel){}

void Client::setTel(string tel){ this->tel=tel;}
string Client::getTel(){return this->tel;}

 

On peut donc accéder aux propriétés de la classe Personne depuis un objet Client

Client z ;
cout<<z.getNom();

On peut également donner un objet fille dans une variable de type mère

Personne  p;

//c est un Client
Client c("Zorg","0554545454");

// p est une Personne, il accepte un Client
 p=c;

Personne p2;
//erreur, c est un client, il n'accepte pas une Personne.
c=p2

Créer une classe gateau et une classe pain. Celles-ci héritent de la classe produit. Le produit possède un prix, une date de fabrication et une date de fin de vente. La classe gateau possède une taille correspondant au nombre de personne, un ingrédient de base. La classe pain possède un type de farine, un type classique ou special, un poids.

La visibilité

Les objets qui héritent d’une classe ont accès :

  • aux propriétés et méthodes public.
  • aux propriétés et méthodes protected.

 

Les objets qui n’héritent pas d’une classe ont accès :

  • aux propriétés et méthodes public.

La redéfinition (overriding)

La redéfinition est le fait de déclarer une méthode avec le même nom et les mêmes paramètres mais dans une classe qui l’hérite.

class A { 
  public: void F1() {} 
}; 
class B : public A { 
  public: void F1() {} 
};

Le mot clef final (bloquer l’overriding)

Une classe final est une classe qui ne peut pas avoir de filles. Une classe final ne peut pas être étendue: le mécanisme d’héritage est bloqué. Mais une classe final peut évidemment être la fille d’une autre classe.

class Client final{}

Une méthode final est une méthode qui ne peut pas être redéfinie dans les sous-classes. Le mot clef final pour un paramètre bloque la modification de sa valeur (constante).

void a() final {}

Le mot clef abstract

Une méthode abstraite ne contient pas de définition, uniquement sa signature. Une méthode abstraite ne peut pas être déclarée static ou private ou final.
Dès qu’une classe contient une méthode abstraite, elle devient abstraite et ne peut pas être instanciée.

class A
{      
public: 
    //méthode virtuel qui devra être redefini dans la classe fille
    virtual void a() = 0; 
};

Le mot clef virtual

class A 
{ 
public: 
    void F1() { cout << "A::F1()\n"; }     
    virtual void F2() { cout << "A::F2()\n"; } 
}; 
  
class B : public A 
{ 
public: 
    void F1() { cout << "B::F1()\n"; }     
    void F2() { cout << "B::F2()\n"; } 
}; 
  
int main() 
{ 
    A a; 
    a.F1(); // affiche "A::F1()" 
    a.F2(); // affiche "A::F2()" 
  
    B b; 
    b.F1(); // affiche "B::F1()" 
    b.F2(); // affiche "B::F2()" 
  
    // copie non polymorphe 
    a = b; 
    a.F1(); // affiche "A::F1()" 
    a.F2(); // affiche "A::F2()" 
  
    // utilisation polymorphe de B (par pointeur) 
    A * pa = &b; 
    pa->F1(); // affiche "A::F1()" 
    pa->F2(); // affiche "B::F2()" <-- grâce à virtual 
  
    // utilisation polymorphe de B (par référence) 
    A & ra = b; 
    ra.F1(); // affiche "A::F1()" 
    ra.F2(); // affiche "B::F2()" <-- grâce à virtual 
}

Dans cet exemple, F1() est redéfinie statiquement par B, c’est-à-dire que le compilateur utilise le type officiel de la variable pour savoir quelle fonction appeler. Ainsi, si on appelle F1() depuis un objet de type A, ce sera toujours A::F1() qui sera appelée, et si l’objet est de type B ce sera B::F1() indépendamment du fait qu’il peut s’agir d’un pointeur ou d’une référence sur A qui désigne en réalité un objet de type B (cas d’utilisation polymorphe de B).
L’appel à une fonction membre virtuel n’est au contraire pas déterminé à la compilation, mais lors de l’exécution. Le fait que A::F2() soit déclarée virtual et supplantée par B::F2() signifie qu’à chaque appel de F2(), le compilateur va tester le type réel de l’objet afin d’appeler B::F2() si possible. Sinon, il appellera A::F2(). On parle alors de liaison dynamique (dynamic binding en anglais) par opposition à la liaison statique faite lors de l’édition de liens.

Accès aux bases de données

Installer le driver de votre base de données.  Exemple https://dev.mysql.com/downloads/connector/cpp/


 

La composition ou l’agrégation

Lorsqu’un objet ne peut exister que s’il est constitué de plusieurs objets, on dit que le premier est composé des autres objets élémentaires. La relation qui en découle est une composition. Cette relation affecte le constructeur.
Si on représente une personne qui est constitué d’un état civil, la classe Personne devra être construit avec un objet de la classe Etat_Civil. Si un objet est composé de plusieurs objets, nous gérons cette composition en utilisant une collection d’objet et non plus un simple objet dans la classe.

 

Les exceptions

Le code peut générer des erreurs à l’exécution. Le programme va alors s’arrêter. Pour éviter que le programme s’arrête, il est possible de capturer ces erreurs.

 

Rattraper une exception

try/catch.

  int a=2;
  int b=0;
  try { if(b == 0) throw string("Division par zéro !"); else return a/b;
  throw 5;
  throw c; 
  } 
  catch(string const& chaine) { // On gère l'exception. 
  } 
  catch(int a) {  // On gère l'exception.
  }
  catch(Client z) {  // On gère l'exception. 
  }

On capture l’erreur grâce au catch. Ici on capture les exceptions de type string.
On notera que l’instruction catch permet de prendre différents types d’exceptions.

La généricité ou templates

La généricité permet de définir des classes avec un (ou plusieurs) type d’objet inconnu, ou générique qui va être déterminé à l’exécution.

std::vector<int> v = {7, 5, 16, 8};

 

Soit la fonction suivante :

int getDouble(int a){
 return a+a;
}

Si jamais nous avons besoin d’une variable de type string ou autre, nous sommes tenté de refaire une méthode.

int getDouble(int a){ return a+a; }
string getDouble(string a){ return a+a; }

En utilisant la généricité, nous n’allons faire qu’une seule classe. Ici on déclare une classe avec un type d’objet que l’on nomme T. La classe peut fonctionner avec n’importe quel type !

template <typename T>
T getDouble(T a)
{
   //truc à faire;
   return T+T;
}

 

cout << getDouble<double>(7) << endl;
cout << getDouble<string>("za") << endl;

Plusieurs types génériques

On peut utiliser autant de type générique que l’on souhaite. Ici on déclare une fonctions avec deux objets génériques que l’on nomme T et Z.

template <typename T, typename Z> 
T getValue(Z a) { //truc à faire; }

On peut aussi réaliser des classes génériques.

 

Le Mapping Objet

Un mapping objet et base relationnel permet de créer l’illusion d’une correspondance entre la base de données et les objets du langage de programmation. On fait donc correspondre les attributs des objets avec les attributs de la base de données. On parle d’ORM (object-relational mapping).

Le pattern DAO consiste à ajouter un ensemble d’objets dont le rôle sera d’aller lire, écrire, modifier, supprimer. Cet ensemble d’objet s’appelle la couche DAO. Ce pattern utilise des classes génériques. On part de nos classes et on réalise des classes qui vont faire la liaison avec la base de données. Ces classes vont porter le nom du pattern : XxxDAO. Ces classes héritent d’une classe DAO qui définit la généricité.

Classe Langage

Notre classe de départ que l’on veut mapper dans la base

class Langage {


    Langage::Langage(long id, String nom):id(id),nom(nom){
    }
    Langage::Langage(){
    }
    long Langage::getId() {
        return id;
    }

    void Langage::setId(long id) {
        this->id = id;
    }
    String Langage::getNom() {
        return nom;
    }
    void Langage::setNom(String nom) {
        this->nom = nom;
    }
}

La classe générique DAO

template <class T>
 class DAO {
    Connection DAO::connect = null ;
    virtual T DAO::select(long id);
    virtual bool  DAO::insert(T obj);
    virtual bool  DAO::update(T obj);
    virtual bool  DAO::delete(T obj);
}

On définit la signature de plusieurs méthodes abstraites qui permettent de manipuler des données.

La classe LangageDAO

Elle hérite de la classe DAO pour MYSQL et implémente les méthodes de celle ci.

class LangageDAO: public DAO{


 Connection LangageDAO::connect {
//connexion à la bd
}

Langage LangageDAO::select(long id){//renvoie un objet Langage selon l'id }
bool LangageDAO::insert(Langage   obj){//insère un objet langage } 
bool LangageDAO::update(Langage   obj){//modifie un objet langage }  
bool LangageDAO::delete(Langage   obj){//supprime un objet langage } 

}

Liaison automatique

Les ORM permettent de gérer l’accès aux données de manière automatique avec ce genre de pattern.

Les Tests

Les tests unitaires

Un test est dit unitaire lorsqu’il teste les méthodes d’une classe de façon atomique et indépendante. Ces tests ne font intervenir en général que la classe et la méthode ciblée par le test.

Les tests d’intégration

Les tests d’intégration permettent de valider que l’application fonctionne correctement après la mise en commun des différentes parties. On peut y voir une double position.

Les tests fonctionnels

Les tests fonctionnels viennent valider le fonctionnement de l’application et du service rendu au client. Ces tests portent en général sur des classes de haut niveau d’abstraction. Ils permettent typiquement de valider un enchaînement d’actions au travers d’un scénario client.

Les tests de performance

La phase de validation permet également de vérifier que les contraintes de performance sont atteintes. Ces tests valident typiquement le passage à l’échelle d’un système et valident la qualité de service maximale.

Les tests de recette

Les tests de recette sont sous la responsabilité du client et sont réalisés par un panel d’utilisateurs représentatifs. Ils mettent en œuvre l’application livrée dans des conditions réelles et vérifient son bon comportement.

Les tests unitaires

Les tests permettent de vérifier que le programme effectue bien les services demandés. Ces tests vont informer les développeurs des erreurs éventuelles du code testé.

Un service correspond à ce qu’on attend de la fonction. Une fonction peut rendre plusieurs services, comme ajouter une valeur à un nombre ET vérifier qu’il est positif.

Pour tester, il faut réaliser quatre étapes :

  1. on définit les services de chaque fonction
  2. on définit par la suite les tests de chaque service
  3. on implémente les fonctions
  4. on teste les fonctions

Le contrôle de résultat

Voici une classe Addition que l’on souhait tester.

class Addition {
    Long calculer(Long a, Long b) {
        return a+b;
    }
    char lireSymbole() {
// Erreur d’implémentation
        return ’-’;
    }
}

La première approche est de tester le code en renvoyant le résultat.

        operation = addition(6,3);
        cout<<"L’addition de 6 et 3 retourne " + operation ;

On pourra comparer directement si l’on obtient ce que l’on souhaite
L’addition de 6 et 3 retourne 9. On s’assure que le résultat obtenu est bien celui attendu. Mais il faut un contrôle humain pour s’assurer du bon résultat.

Dans un deuxième temps, on peut demander à l’utilisateur de renvoyer un résultat binaire : OK.

        operation = addition(6,3);
        if (operation ==9)
           cout<<"Test OK";
        else
          cout<<"Test KO";

La console affiche maintenant :

Test OK

Rapport de tests

Mais avec plusieurs tests sur plusieurs méthodes, il va falloir déterminer où est l’erreur.

Test OK
Test KO
Test KO
Test OK
Test KO
Test KO

Création de tests unitaires

Dans la plupart de langages, on peut utiliser des bibliothèques qui génèrent un rapport de test ce qui facilite la programmation pour des applications complexes ( fastidieux pour les petits programmes). En C++, deux bibliotheques sont très utilisées, boost.test et cppUnit.