Cours C++ #0
Théorie
Contenu:
0. Le "Monde" orienté-objets
1. 1er programme pour imprimer "Bonjour GIREF!"
2. 2e programme pour additionner 2 nombres entrés par l'usager
3. Opérateurs: +=, -=, ++, --
4. Structures de contrôle
5. Les  fonctions
6. Les énumérations
7. Les références: Passage par référence vs par valeur
8. Les arguments par défaut d'une fonction
9. Opérateur de portée "::"
10. Surcharge de fonctions
11. Patrons de fonctions <<template>>


0. Le "Monde" orienté-objets

- L'orienté-objets traduit les objets de la réalité par des objets programmés dans un langage comme C++ ou Java.
- Comme les objets de la réalité, les objets programmés ont des propriétés et des fonctionnalités possibles/prévues.
- Le langage orienté-objets est basé sur la communication entre objets.
- Les langages orientés-fonctions comme Fortran ou C sont basés sur la suite d'appels de fonctions dans lesquelles on passe les données.
- L'orienté-objets transporte les données avec les fonctionnalités.
- On parle souvent de validation des données, contrôle des données, vérification des données.
- Le langage orienté-objets met beaucoup plus l'accent sur les données qu'un langage orienté-fonctions.

1. 1er programme pour imprimer "Bonjour GIREF!"

Voici un programme très simple:

#include <iostream>

int main()
{
    // Imprime "Bonjour GIREF! à l'écran (cout)"
    cout << "Bonjour GIREF!" << endl;
    return 0;
}

Une fois le fichier créé (et nommé par exemple giref.cc), il suffit de taper :

g++ -g -c giref.cc
g++ -o giref giref.o
pour compiler et créer le lien, puis:
./giref
pour exécuter le programme.

- Le #include est un fichier qui sera inclus par le préprocesseur et qui déclare de multiples fonctionnalités (fonctions, classes, variables globales) relatives aux flux d'entrée/sortie.
- Dans le standard, on ne met pas de .h pour les fichiers d'en-tête i.e. #include <iostream> au lieu de #include <iostream.h> (header files)
- Dans le standard, on devrait aussi ajouter using namespace std pour que le programme fonctionne
- Il existe 2 types de commentaires:
    1- les //qui font que le reste de la ligne est ignoré
    2- les /* avec */ qui font que tout ce qui est entre les deux est un commentaire
- cout est une variable (objet) du namespace std qui permet d'écrire à l'écran.
- endl est une fonction du namespace std qui fait une fin de ligne (à l'écran comme dans un fichier).

2. 2e programme pour additionner 2 nombres entrés par l'usager

#include <iostream>

int main()
{
    int lNombre1, lNombre2;

    // cin nous permet de lire au clavier
    cin >> lNombre1 >> lNombre2;
    cout << (lNombre1 + lNombre2) << endl;

    return 0;
}

3. Opérateurs: +=, -=, ++, --

- Les opérateurs ++ et -- ont pour effet d'augmenter/décrémenter de 1 la variable à laquelle on l'applique.
- Il existe 2 versions de l'opérateur ++: préincrément et postincrément

Exemple :

#include <iostream>

int main()
{

    int lNombre1 = 9;

    lNombre1 +=1;
    lNombre1++;
    cout << lNombre1 << endl;
    cout << lNombre1++ << endl;
    cout << ++lNombre1 << endl;

    return 0;
}

Quelle est la sortie de ce programme?

4. Structures de contrôle

  a- if/else
Exemple :

#include <iostream>

int main()
{
    int lNombre1(0);
    cin >> lNombre1;

    if (7 == lNombre1) {
        cout << "Le 7 chanceux!" << endl;
    }
    else {
        cout << "Désolé..." << endl
    }

    return 0;
}

   b- while
Exemple :

#include <iostream>

int main()
{
    int lNombre1(0);

    cin >> lNombre1;

    // On vérifie que le cin est "valide"...
    while (999 != lNombre1 && cin) {

        if (7 == lNombre1) {
            cout << "Le 7 chanceux!" << endl;
        }
        else {
            cout << "Désolé..." << endl
        }
        cin >> lNombre1;
    }

    return 0;
}

   c- for
Exemple :

#include <iostream>

int main()
{
    int lNombre1(0);

    cin >> lNombre1;

    for (int i = 0; i < lNombre1; ++i) {
        cout << i << endl;
    }

    return 0;
}

    d- aiguillage (switch en anglais)

Le switch nous permet de remplacer une série de if/else imbriqués.
Exemple :

#include <iostream>
int main()
{
    int lNombre1(0);

    cin >> lNombre1;
    if (10 == lNombre1) {
        cout << "10 moutons.." << endl;
    }
    else if (9 == lNombre1) {
        cout << "9 moineaux..." << endl;
    }
    else if (...) {
        ...
    }
    return 0;
}

Avec switch:

#include <iostream>
int main()
{
    int lNombre1(0);

    cin >> lNombre1;
    switch (lNombre1) {
        case 10 : {
            cout << "10 moutons.." << endl;
            break;
        }
        case 9 : {
            cout << "9 moineaux..." << endl;
            break;
        }
        // Un "default" dans un switch c'est comme une assurance!
        default: {
            cout << "Erreur, pas connu..." << endl;
        }
    }
    return 0;
}

5. Les fonctions

- En général, on essaie d'avoir 1 point entrée et 1 point sortie, cela rend la fonction plus lisible.
 

Déclaration vs définition:
    - Une déclaration est ce qu'on appelle la signature ou le prototype de la fonction (son type de retour, son nom, son nombre de paramètres et le type de chacun des paramètres).
    - Une définition est le code même de la fonction, c'est-à-dire le contenu de la fonction.

Exemple : La norme d'un vecteur x,y,z.

#include <iostream>

double norme(double pX, double pY, double pZ); // Déclaration

int main()
{
    double lX = 4.0, lY = 3.0, lZ= 5.0;
    cout << norme(lY, lY, lZ) << endl;

    return 0;
}

double norme (double pX, double pY, double pZ) // Définition
{
    return sqrt(pX*pX + pY*pY +pZ*pZ);
}

6. Les énumérations

- Elles ne sont utilisées qu'à l'occasion.
- Une énumération définit un nouveau type en C++.  Ce n'est PAS un entier.

- Les jours de la semaine:

#include <iostream>

enum Jour {Lundi = 111, Mardi, Mercredi, Jeudi, Vendredi, Samedi, Dimanche};

int main()
{
    Jour lMonJourPrefere = Samedi;
    cout << lMonJourPrefere << endl;

    return 0;
}

7. Les références: Passage par référence vs par valeur

- Le passage par valeur fait une copie de la valeur qui est passée.  Il existe alors 2 copies de la même valeur dans 2 cases mémoires distinctes.
- Le passage par référence est préférable au passage par valeur étant donné qu'il ne fait pas de copie de la variable passée, mais donne une référence à la case mémoire qu'il occupe. Il existe alors une seule copie de la valeur et donc une seule case mémoire occupée.  La fonction qui reçoit la variable par référence doit cependant faire attention car elle peut modifier cette valeur.
- Faire un passage par valeur pour un entier ou un double reste encore acceptable.  Par contre, faire une copie d'une matrice ou d'un maillage serait inacceptable.
- La référence constante nous permet de n'avoir qu'une seule copie de la valeur stockée, sans toutefois nous permettre de la modifier.

Exemple :

#include <iostream>

int calculeCarreParValeur(int pEntier);  // passage par valeur
void calculeCarreParReference(int& pEntier); // passage par référence

int main()
{
    int lMaValeur = 10;

    cout << "lMaValeur initiale:" << lMaValeur << endl;
    cout << " Carre par valeur:" << calculeCarreParValeur(lMaValeur) << endl;
    cout << "lMaValeur:" << lMaValeur << endl;

    calculeCarreParReference(lMaValeur);
    cout << " Carre par reference:" << lMaValeur << endl;

    return 0;
}

int calculeCarreParValeur(int pEntier)
{
  return pEntier*pEntier;
}

void calculeCarreParReference(int& pEntier)
{
  pEntier=pEntier*pEntier;
}

8. Les arguments par défaut d'une fonction

- On peut définir des arguments par défaut à une fonction.
- Utile mais dangereux! Attention à la lisibilité du code!!!

Exemple :

#include <iostream>

double calculVolumeBoite(const double& pLarg = 1,
                         const double& pHaut = 1,
                         const double& pProf = 1);

int main()
{
    cout << calculVolumeBoite() << endl; // Quel est le sens de cela?

    cout << calculVolumeBoite(2,4) << endl;

    cout << calculVolumeBoite(2,4,5) << endl;

    return 0;
}

double calculVolumeBoite(const double& pLarg,
                         const double& pHaut,
                         const double& pProf)
{
    return pLarg * pHaut * pProf;
}

9. Opérateur de portée "::"

-L'opérateur de portée ( scope en anglais) permet de spécifier le nom d'une variable, classe ou fonction qui n'est pas dans la portée actuelle. Par exemple, on pourrait avoir une variable globale et locale du même nom.
- On utilise aussi l'opérateur de portée pour aller chercher des définitions dans un namespace (ex. : std::cout).

#include <iostream>
#include <iomanip>

const double PI = 3.14159265358979;

int main()
{
    const float PI = 3.14;

    cout << setprecision(20)    // Nouveau! ajuste le nombre maximal de décimales écrites.
         << " PI local: " << PI << endl
         << " PI global: " << ::PI << endl;

    return 0;
}

10. Surcharge de fonctions

- On peut avoir plusieurs fonctions qui portent le même NOM, mais qui ont des paramètres et types de retour différents.
- On peut faire de la surcharge en faisant varier au moins le nombre de paramètres ou le type de paramètres.
- On ne peut pas surcharger une fonction seulement par le type de retour (Ah! le C++....).
- Réflexion: l'opérateur + en C, Fortran, C++ est surchargé selon qu'il s'applique sur un entier ou un double. Hmmm...

Exemple :

int calculCarre (int pNombre);
double calculCarre (double pNombre);

11. Patrons de fonction modèle(template en anglais)

- Les patrons de fonction permettent au compilateur de CRÉER le code C++ de la fonction, selon le type de paramètre que la fonction reçoit.
- Cela permet de n'écrire qu'une seule fois le code que l'on aurait dû répéter plusieurs fois si on avait surchargé la fonction.

Exemple :

#include <iostream>
#include <iomanip>

template <class PTType>
PTType min(const PTType& pA, const PTType& pB)
{
return (pA < pB ? pA : pB);
}

int main()
{
    int lNombre1, lNombre2;

    cin >>lNombre1 >> lNombre2;
    cout << "Min: " << min(lNombre1, lNombre2) << endl;

    double lDble1, lDble2;

    cin >> lDble1 >> lDble2;
    cout << "Min: " << min(lDble1, lDble2) << endl;

    return 0;
}