Aller au menu - Aller au contenu

[Plan du site] Vous êtes ici --- > Le Site du Zér0 > Les tutoriels > Officiels > Programmation > Liste de toutes les notes

Apprenez à programmer en C++ !

Auteur : M@teo21
Créé : le 18/09/2007 17:13:58
Modifié : Hier à 11:02:27
Avancement : 100%
Imprimer tout le tutoriel
Ce cours est la suite de "Apprenez à programmer en C"
Il est nécessaire d'avoir lu au moins les parties I et II du cours de C pour pouvoir suivre celui-ci sur le C++



Après avoir découvert le langage C dans le cours "Apprenez à programmer en C", nous nous intéressons ici au C++ :)
Le langage C++ est basé sur le C : ce que vous avez donc appris jusqu'ici va vous resservir, pour ne pas dire vous être indispensable !

Les modifications entre le C et le C++ sont nombreuses. La plus importante d'entre elles est l'introduction de la Programmation Orientée Objet, que l'on abrège couramment POO. On en entend souvent parler, mais qu'est-ce que c'est concrètement ?

Après une première partie très importante sur les notions de base du C++, nous passerons à la pratique et verrons comment créer des applications fenêtrées qui fonctionnent aussi bien sous Windows, Linux et Mac OS à l'aide de la bibliothèque Qt !

Image utilisateur Image utilisateur Image utilisateur
Exemples tirés de la partie II sur Qt

Ce cours est composé des parties suivantes :

Partie 1 : [Théorie] La Programmation Orientée Objet

Le langage C++ est basé sur le C : ce que vous avez donc appris dans le cours de C jusqu'ici va vous resservir, pour ne pas dire vous être indispensable !

Les modifications entre le C et le C++ sont nombreuses. La plus importante d'entre elles est l'introduction de la Programmation Orientée Objet, que l'on abrège couramment POO. On en entend souvent parler, mais qu'est-ce que c'est concrètement ?
La réponse se trouve dans cette partie du cours ;)

Introduction au C++

Le C++, enfin on y arrive ! :D

J'espère que vous êtes encore en forme, car vous avez des tonnes de choses à apprendre (et pas des plus faciles :p ). Mais courage, si vous êtes arrivés là, vous serez capable de réussir à comprendre la suite ça ne fait aucun doute ;)

Le C++ ressemble au C en apparence, mais vous allez vite vous rendre compte qu'il est en fait bien différent.

Ce chapitre servira d'introduction. Nous allons commencer par voir ce qui différencie le C du C++ et quels sont les défauts du C qui ont conduit à la réalisation du C++.

Rappel IMPORTANT pour ceux qui n'auraient pas lu l'avertissement sur le sommaire : ce cours est la suite du cours de C. Vous devez avoir lu au moins les parties I et II du cours de C avant de vous attaquer à la lecture de ce cours sur le C++, sinon vous n'allez rien comprendre !

Pourquoi avoir créé le C++ ?

Je vous l'ai dit dès le tout début du premier chapitre du cours, et je ne vous ai pas menti : le langage C n'est pas limité. Vous pouvez faire tout ce que vous voulez avec, des fenêtres (avec GTK+ par exemple), de la 2D (avec SDL), de la 3D (avec OpenGL)... Le tout est de trouver la librairie qui propose ce dont vous avez besoin.

Alors du coup on peut se demander... pourquoi un gars s'est levé un jour et a dit : "Aujourd'hui je vais inventer un nouveau langage" ?
Le type en question s'appelle Bjarne Stroustrup, il est danois et il est l'auteur d'un des langages de programmation les plus utilisés dans le monde aujourd'hui. Et il n'est pas fou : il n'a pas fait ça pour le plaisir (enfin j'espère) !


Le C a des avantages



Maintenant que vous programmez en C, vous avez pu vous rendre compte certainement que celui-ci a un grand nombre d'avantages :



Bref, autant de raisons valables d'aimer le C ^^


Mais le C a aussi des défauts !



Si le C n'avait que des avantages, ce serait le langage parfait. Or, les programmeurs savent très bien que le langage parfait n'existe pas. Tout langage a des défauts.

Donc le C n'est pas parfait. Que lui reproche-t-on ?
Déjà, certains concepts de programmation plus récents manquent :



Mais le vrai problème du langage C est qu'il n'est pas prévu pour faire de la Programmation Orientée Objet, une technique de programmation particulièrement efficace apparue récemment.

La programmation orientée quoi ?

Non, ce n'est pas une insulte.
Bon d'accord, il faut avouer que quand quelqu'un nous dit : "Je fais de la programmation orientée objet", on a tendance à s'éloigner un peu de peur que ça soit contagieux :-°


Beaucoup de gens parlent ou ont entendu parler de Programmation Orientée Objet (que j'abrègerai maintenant tout le temps POO, ça va plus vite :p ). Mais concrètement, la POO c'est quoi ?

La POO est un concept de programmation, une façon de programmer. Ce n'est pas un langage.
Le C++, lui, est un langage. Il a été principalement inventé pour faciliter l'utilisation de la POO.

La POO n'est pas utilisée qu'en C++. De nombreux langages, encore plus récents, exploitent au maximum les concepts de la POO. Je pense en particulier à Java et Python, mais il y en a bien d'autres.
Par ailleurs, il est possible de faire de la POO en C, mais c'est assez compliqué.


Bon, à quoi ça sert la POO ?



C'est une façon de programmer qui permet de rendre un code source plus facilement réutilisable, plus facile à modifier.

Comment ça ? Les programmes écrits en C sont difficiles à modifier ?


Ah non, je n'ai pas dit ça :p
Simplement, quand le programme devient gros, il faut avouer qu'on finit assez facilement par se perdre dans toutes les fonctions qu'on a créées. La POO nous permet de mieux organiser notre code source, de lui donner une certaine logique. Les avantages de cette meilleure organisation sont nombreux, vous les découvrirez progressivement.

En fait, c'est un peu comme les pointeurs : vous n'en avez pas forcément compris l'intérêt tout de suite, mais je suis sûr que maintenant vous ne pouvez plus vous en passer ! ;)


L'idée à la base de la POO



En C, vos programmes ne sont au final qu'un ensemble de fonctions accessibles de partout qui manipulent des tonnes de pointeurs. Même si vous pouvez faire plusieurs fichiers, cela ne suffit pas toujours à organiser correctement votre programme.

Par exemple, vous ne pouvez pas avoir deux fonctions nommées ajouter dans votre programme. Même si ces fonctions sont dans deux fichiers différents !
Ainsi, si vous avez une fonction ajouter permettant d'ajouter des heures et ailleurs une autre fonction ajouter permettant d'ajouter des euros, le langage C sera perdu et vous dira qu'il ne sait pas quelle fonction appeler quand vous demandez la fonction ajouter.

Imaginez que toutes les fonctions d'un programme en C nagent dans une seule et même grande piscine. C'est ce qu'on appelle l'espace global. Les fonctions évoluent toutes dans un même espace.

Espace global en C



Imaginez maintenant si on séparait ces fonctions. On place la fonction ajouter spécialisée dans les heures dans une piscine, et on place la fonction ajouter spécialisée dans les euros dans une autre piscine. Du coup, vous n'avez plus qu'à vous rendre devant la piscine qui vous intéresse (par exemple la piscine spécialisée dans les heures) et vous pouvez alors appeler la fonction ajouter. Il n'y a pas de risque de conflit cette fois, parce que les fonctions sont confinées dans des espaces différents ! On ne les mélange plus ;)

Objets en C++
En C++, les fonctions sont compartimentées dans des espaces différents


Dans le schéma ci-dessus, j'ai créé 2 espaces pour séparer les fonctions ajouter. J'ai nommé ces espaces pour les identifier. On a maintenant :



Vous noterez que les autres fonctions comme main nagent encore dans l'espace global. Il est en effet toujours possible de mettre des fonctions dans l'espace global en C++.
D'autres langages interdisent carrément ce genre de choses (je pense à Java par exemple). Java est un langage complètement orienté objet : toutes les fonctions sont obligatoirement confinées dans des espaces particuliers et non dans l'espace global. Le C++ autorise encore de mettre des fonctions dans l'espace global, et c'est d'ailleurs en partie ce que certains lui reprochent (je vous rappelle que le langage parfait n'existe pas :p ).


Allons un peu plus loin



Est-ce qu'on ne peut mettre qu'une seule fonction par espace ?

Non, bien sûr ! Le but, c'est de regrouper les fonctions de notre programme par "thème".

Prenons par exemple le thème Temps.
On peut ajouter des heures, mais on pourrait aussi créer la fonction qui retire des heures, une autre qui donne l'heure actuelle, etc.

De même pour le thème Argent. Disons que ça c'est l'argent que vous avez.
Vous pouvez aussi retirer de l'argent, créer la fonction qui vous donne la quantité d'argent que vous avez sur votre compte en banque, etc.

Ajoutons ces fonctions dans notre schéma :

Objets en C++
Regroupez vos fonctions par thème !


Vous noterez qu'il y a 2 fonctions retirer : là encore ça ne pose pas de problème car elles sont dans 2 espaces différents.


Eh bien, croyez-moi si vous voulez, mais si vous avez compris ce que je viens d'expliquer à l'instant, vous avez déjà une bonne petite idée de ce qu'est la programmation orientée objet !

Maintenant je vous rassure : ça c'est vraiment la base de la base. La POO implique pas mal de règles, et l'organisation est parfois un peu déroutante. On va parler de POO dans pratiquement toute cette partie du cours, vous aurez donc le temps de vous rendre compte à quel point le sujet est riche ^^

Comme la POO est un vaaaaste sujet, nous n'allons pas l'aborder immédiatement (je veux pas vous tuer de suite :p ).
Comme je vous l'ai dit au début, il y a de nombreuses différences entre le C et le C++. La plus importante d'entre elles est l'introduction de la POO, mais ce n'est pas la seule. Il y a aussi une foule de petites nouveautés pas bien compliquées à comprendre.

Voilà ce qu'on va faire :

  1. Dans un premier temps nous allons découvrir toutes ces petites nouveautés qui n'ont aucun rapport avec la POO (ça prendra environ 3 chapitres).
  2. Ensuite nous attaquerons la POO et je vous en ferai manger jusqu'à la fin de cette partie du cours :D

On attaque notre premier programme C++ dès le chapitre suivant. Nous y découvrirons la notion de flux d'entrée / sortie. Une sorte de... mise en bouche quoi :p

Premier programme C++ avec cout et cin

Après un bref chapitre d'introduction, nous pouvons commencer à coder nos premières lignes de C++ :)

Ce chapitre sera assez simple : nous verrons quelles techniques on utilise en C++ pour afficher du texte à l'écran (dans une console) et comment on récupère du texte saisi au clavier. Vous allez voir que c'est assez différent ce qu'on connaissait en C avec printf et scanf.

Nous réutiliserons cela dans tout le cours de C++. Soyez donc attentifs, et ça ne devrait pas vous poser de problème ;)

Configurer l'IDE pour le C++

Jusqu'ici, vous n'avez créé dans votre IDE que des projets en C.
Or, en C++ on utilise un autre compilateur. Par exemple, il y a gcc qui est un compilateur C, et g++ qui est un compilateur C++. Il va donc falloir dire à votre IDE que vous allez faire du C++, sinon il appellera le mauvais compilateur (et là ça risque pas de marcher :-° ).

Lancez donc votre IDE favori. Pour ma part, vous l'aurez compris dans les chapitres précédents, je travaille principalement sous Code::Blocks. Si vous avez un autre IDE comme Visual C++ ou Dev C++ ça marche aussi ;)

Créez un nouveau projet de type console (eh oui, on retourne à la console pour faire nos expériences) et pensez à bien sélectionner C++.

Nouveau projet C++
Création d'un nouveau projet. Veillez à bien sélectionner C++


Si vous avez Dev C++ la manipulation est la même, je ne vous refais pas de screenshot vous êtes grands maintenant ^^
Sous Visual C++, le projet est par défaut compilé en C++, vous n'aurez donc pas besoin de spécifier quoi que ce soit.


Cliquez sur "Create" pour créer le nouveau projet.
Code::Blocks crée un premier fichier nommé main.cpp dans le projet avec quelques premières lignes de code C++.

En C++, vos fichiers .c ont l'extension .cpp. Les fichiers .h, eux, gardent, l'extension .h.
Certains trouvent cela illogique et ont choisi à la place d'utiliser .cc (pour les sources C++) et .hh (pour les headers C++). Ne soyez donc pas surpris par ce type de notation ;)


Voici le code de base que nous propose Code::Blocks dans notre nouveau projet :

Code : C++
1
2
3
4
5
6
7
#include <iostream>

int main()
{
        std::cout << "Hello world!" << std::endl;
        return 0;
}


Si vous avez un autre IDE, supprimez le code qui a été généré et utilisez celui-ci à la place pour qu'on soit sûrs de travailler sur le même code ;)

Analyse du premier code source C++

Intéressons-nous maintenant à chacune de ces lignes de code et voyons ce qui change pour le moment par rapport au C.

Include



Code : C++
1
#include <iostream>


On reconnaît là une bonne vieille directive de préprocesseur.

2 choses :


On inclut donc ici la librairie iostream qui contient les outils nécessaires pour afficher du texte dans la console et récupérer la saisie au clavier.

Fonction main()



Code : C++
1
2
3
4
5
int main()
{
    // ...
    return 0;
}


On retrouve notre fonction main habituelle. Cela fonctionne comme en C : tout programme commence par la fonction main(). Rien de choquant ici ;)
La fonction retourne 0, ce qui est là encore logique puisque la fonction doit retourner un int.

Pour info, 2 formes de main sont possibles. Celle-ci :
int main()
... mais aussi cette forme un peu plus compliquée que vous connaissez aussi :
int main(int argc, char *argv[])
La seconde forme permet de récupérer les arguments d'appel du programme, ce qu'on ne fait pas toujours. Vous pouvez donc vous contenter de la première forme qui est surtout plus simple à retenir ;)


cout



Code : C++
1
std::cout << "Hello world!" << std::endl;


La première ligne du main est la plus intéressante, c'est d'ailleurs la seule qui doit vraiment vous surprendre.
En effet, ça ne ressemble pas à un appel de fonction, il y a plein de signes bizarres, pas de parenthèses comme on a l'habitude dans un appel de fonction. Bon sang de bonsoir qu'est-ce que c'est ? o_O

On va découvrir ça maintenant plus en détails ;)

Le flux de sortie cout

cout n'est pas une fonction mais un flux, un élément nouveau introduit en C++. Notez que ça n'a rien à voir avec la POO pour le moment ;)

Rassurez-vous, les fonctions existent toujours en C++ (d'ailleurs vous avez vu qu'il y a un main()), mais vous vous rendrez compte petit à petit qu'on ne les utilise plus de la même manière.


Le flux cout est l'équivalent de la fonction printf... mais en mieux, en plus simple :D

Revoyons cette fameuse ligne de code :

Code : C++
1
std::cout << "Hello world!" << std::endl;


Il y a deux mots-clé particuliers dans cette ligne : cout et endl. Vous noterez qu'ils ont tous les deux le préfixe std::
Tous les mots-clé de la librairie standard du C++ utilisent ce préfixe. Théoriquement, on est obligé de le mettre à chaque fois, mais c'est un peu lourd.
On a heureusement une solution pour se simplifier la vie. Rajoutez cette ligne de code avant le main() :

Code : C++
1
using namespace std;


Du coup, vous pouvez virer tous les préfixes std::
Ca rend déjà notre code un peu plus facile à lire :

Code : C++
1
2
3
4
5
6
7
8
9
#include <iostream>

using namespace std;

int main()
{
        cout << "Hello world!" << endl;
        return 0;
}


Voilà un premier point de réglé. Nous ne rentrerons pas dans le détail de ce "using namespace" pour le moment (pour ne pas compliquer inutilement les choses).

Intéressons-nous de plus près à cette ligne avec cout, désormais plus lisible :

Code : C++
1
cout << "Hello world!" << endl;


Vous voyez qu'il y a un nouveau symbole : le chevron <
On le rencontre d'ailleurs toujours par paire, comme ceci : <<

Imaginez que ces chevrons représentent en fait des flèches. Du coup, la ligne se lit de droite à gauche :

  1. On prend le mot-clé endl, qui signifie retour à la ligne.
  2. On l'insère à la fin de la chaîne "Hello world!"
  3. On insère le résultat obtenu dans cout.


cout est la contraction de "c-out" (console out = sortie console). En bon français, vous devez prononcer cela "Ci Aoute" :p
cout représente la sortie en C++ (out = sortie). La sortie d'un programme, ben c'est tout simplement l'écran. Donc cout représente l'écran ;)

Résultat des courses, ce code signifie que le texte "Hello world!" suivi d'un retour à la ligne est envoyé vers l'écran. Il faut bien imaginer que les chevrons << indiquent le sens dans lequel les données sont envoyées.

Cette ligne de code commande donc un affichage de texte à l'écran. Compilez et exécutez le programme pour voir :

Code : Console
Hello world!


Revenons un peu sur endl.
endl est un mot-clé qui signifie "fin de ligne" (end line en anglais). En fait, c'est tout bêtement un mot qui remplace le \n que vous connaissez du C, qu'on utilisait pour faire des sauts de ligne.

Ah bon, on ne peut plus utiliser \n en C++ ?


Si si. En fait, le mot-clé endl a été entre autres introduit pour améliorer la lisibilité du code source (pour ne pas qu'on mélange le "n" avec le texte à afficher).
Vous pouvez d'ailleurs tester, vous verrez que l'\n fonctionne toujours :

Code : C++
1
cout << "Hello world!\n";


Le résultat à l'écran est exactement le même :

Code : Console
Hello world!


Désormais, j'utiliserai endl à la place de \n dans la suite du cours.


L'intérêt de cout



Pour le moment, vous devez vous dire que cout ressemble étrangement à la fonction printf, avec juste des symboles << en plus pour vous embrouiller l'esprit :-°

En fait, c'est encore plus facile à utiliser que printf. L'intérêt se voit notamment lorsqu'on veut afficher le contenu d'une variable.
Regardez ce code, c'est super simple :

Code : C++
1
2
3
4
5
6
int main()
{
    int age = 21;
    cout << "Salut, j'ai " << age << " ans" << endl;
    return 0;
}


Code : Console
Salut, j'ai 21 ans


Voilà, c'est assez intuitif pour que je n'aie pas besoin de vous expliquer comment ça marche ;)

Ce qui est génial, c'est qu'on n'a plus besoin de s'embêter avec la syntaxe des printf : %d, %ld, %lf, %s, %c etc... Ici, le langage est plus intelligent, il reconnaît le type de variable qui lui est envoyé.

Sceptiques ? Ok, essayez d'ajouter une variable de type chaîne de caractère dans ce cout :

Code : C++
1
2
3
4
5
6
7
int main()
{
    int age = 21;
    char pseudo[] = "M@teo21";
    cout << "Salut, j'ai " << age << " ans et je m'appelle " << pseudo << endl;
    return 0;
}


Code : Console
Salut, j'ai 21 ans et je m'appelle M@teo21


Vous voyez, on a envoyé d'un coup à cout un entier et une chaîne de caractères, sans préciser le type de variable, et il n'a pas bronché :p

Pour rappel, en C on aurait dû faire :

Code : C
1
printf("Salut, j'ai %ld ans et je m'appelle %s\n", age, pseudo);


Non seulement il fallait se souvenir des codes %ld et %s, mais en plus les variables utilisées sont indiquées à la fin. En C++ avec cout, comme vous avez pu le constater, la variable est placée au milieu, ce qui rend le code plus facile à lire :)

Bien entendu, vous n'êtes pas limité à un seul cout par programme ^^
Vous pouvez tout à fait faire plusieurs cout si vous le voulez :

Code : C++
1
2
cout << "Salut, j'ai " << age << " ans" << endl;
cout << "Je m'appelle " << pseudo << endl;


Résultat :

Code : Console
Salut, j'ai 21 ans

Je m'appelle M@teo21


Le endl à la fin de chaque cout n'est pas obligatoire. Si vous l'enlevez, il n'y aura juste pas de retour à la ligne.
Entraînez-vous un peu avec cout, vous devriez vous y habituer rapidement !

Le flux d'entrée cin

Après avoir vu comment afficher du texte, voyons voir maintenant comment récupérer du texte saisi au clavier.
Là encore, ça fonctionne avec un système de flux, et vous allez voir que c'est un vrai régal de simplicité :D

Le mot-clé à connaître ici est cin. Il vient en remplacement de la pratique (mais complexe) fonction scanf du langage C.
cin est la contraction de "c-in", ce qui signifie entrée console. Prononcez ça comme il faut siouplaît : "Ci in" :p

cin représente l'entrée en C++. Et qu'est-ce que l'entrée ? C'est le clavier ! Eh oui, c'est par le clavier qu'on entre les données.

cin représente donc le clavier et permet de récupérer du texte saisi par l'utilisateur.
Tenez, on va faire un truc super original : on va lui demander son âge :-°

Regardez comment ça fonctionne :

Code : C++
1
cin >> age;


Si vous êtes un peu observateur, 2 choses doivent vous avoir choqué :



Ne confondez pas le sens des flèches entre cout et cin. Le principe c'est que les données transitent dans un sens précis : de la mémoire vers l'écran (cout) ou du clavier vers la mémoire (cin).
Les flèches sont donc dans le sens de déplacement des informations. Avec un peu de bon sens, vous ne devriez pas vous tromper ;)


Programmes de test de cin



Testons maintenant cin dans un petit programme. Voici le code complet :

Code : C++
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
#include <iostream>

using namespace std;

int main()
{
    int age = 0;
    cout << "Quel age avez-vous ?" << endl;
    cin >> age;
    cout << "Ah ! Vous avez donc " << age << " ans !" << endl;
    return 0;
}


Code : Console
Quel age avez-vous ?

21

Ah ! Vous avez donc 21 ans !


Désolé si je me répète, mais je trouve cela d'une simplicité effarante ^^
Finis les %ld qui nous agacent, finis les oublis de symbole & dans les scanf, finis :D
Bon d'accord, cette nouvelle syntaxe surprend un peu quand on a fait pas mal de C avant, mais on s'y fait vite rassurez-vous.


On peut aussi s'entraîner à demander le pseudonyme de l'utilisateur :

Code : C++
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
#include <iostream>

using namespace std;

int main()
{
    char pseudo[50];
    cout << "Quel est votre pseudo ?" << endl;
    cin >> pseudo;
    cout << "Salut " << pseudo << endl;
    return 0;
}


Code : Console
Quel est votre pseudo ?

M@teo21

Salut M@teo21


Alors, premières impressions du C++ ? :euh:

"Ca change" ? Ah ben oui un peu, c'est sûr :D
En effet, la syntaxe des flux cout et cin surprend un peu... mais je suis sûr que vous serez convaincu comme moi que grâce à ce système de nombreuses choses sont simplifiées !

On va continuer notre tour d'horizon des ajouts-au-C++-qui-n'ont-rien-à-voir-avec-la-POO dans le chapitre suivant.
Au programme, nous allons découvrir les changements au niveau de la gestion des variables. Nous verrons en particulier ce que sont ces mystérieuses références dont vous avez entendu parler.

Nouveautés pour les variables

Nous continuons notre tour d'horizon des nouveautés du C++ dans ce chapitre. Nous n'allons pas encore voir ici la POO, mais patience, ça ne saurait tarder ;)

Nous nous intéresserons aux nouveautés relatives aux variables, c'est-à-dire à la gestion de la mémoire. Nous découvrirons entre autres le type bool, les modifications par rapport aux définitions de variables, les allocations dynamiques en C++ et les références.

Rien de bien difficile au programme donc, mais il s'agit de nouveautés importantes, donc à ne pas négliger.

Le type bool

On a découvert dès le début du cours de C qu'il existait un grand nombre de types de variable différents :


Le problème s'est posé lorsqu'on a voulu stocker des booléens. Faute d'avoir un type de donnée spécialisé dans le stockage des booléens, on a fait comme la plupart des programmeurs C font : on a utilisé un type entier, comme int.

Rappel. Un booléen est une variable qui peut prendre 2 valeurs : vrai ou faux. Par exemple, "majeur" est un booléen : soit on est majeur, soit on ne l'est pas :p
On dit que le nombre 0 représente "Faux", tandis que le nombre 1 représente "Vrai" (en fait, tout nombre différent de 0 signifie "Vrai").


Or, si on utilise int pour les booléens, on risque de les confondre avec des variables destinées à stocker des nombres, puisque int est à la base fait pour stocker des nombres !

Petit exemple tout simple :

Code : C
1
2
int majeur = 1;
int age = 21;


La variable majeur est un booléen, car elle signifie soit vrai soit faux.
La variable age, elle, est un nombre. Elle peut valoir par exemple 21.

Mais comment fait-on pour savoir laquelle de ces variables est un booléen et laquelle est un nombre ?
On peut se baser sur le nom de la variable, c'est sûr, mais il aurait été plus pratique et plus clair d'avoir un type spécial pour les booléens.

Ca tombe bien ! Il y a justement en C++ un nouveau type de base : le type bool. Toute variable de ce type peut prendre 2 valeurs :


(je vous conseille de retenir ces 2 valeurs par coeur, vous en aurez besoin :-° )

Du coup, le code qu'on a vu plus haut s'écrirait comme ceci en C++ :

Code : C++
1
2
bool majeur = true;
int age = 21;


Voilà une bonne chose qui nous permettra d'éviter des ambigüités dans nos programmes :)

Il n'y a pas de guillemets autour de true, car c'est un mot-clé du langage C++. Ce n'est pas une chaîne de caractères !


Rappel : les booléens dans les conditions



Je tiens juste à vous faire un petit rappel. Si vous avez bien suivi le cours de C, ça ne devrait pas vous choquer ;)

En théorie, on peut tester un booléen comme ceci dans une condition :

Code : C++
1
2
3
4
if (majeur == true) // S'il est majeur (forme longue)
{
    // ...
}


Mais en général, si la variable a un nom clair, on préfèrera enlever la partie == true. C'est tout à fait possible et l'ordinateur le comprend très bien :

Code : C++
1
2
3
4
if (majeur) // S'il est majeur (forme courte)
{
    // ...
}


Ce code est plus lisible et plus court que le précédent. On comprend bien que la condition est "S'il est majeur".

Par ailleurs, le point d'exclamation sert à exprimer la négation. Dans notre cas, ce code signifierait "S'il n'est pas majeur" :

Code : C++
1
2
3
4
if (!majeur) // S'il n'est PAS majeur
{
    // ...
}


Ce n'est pas une nouveauté du C++ car ça existait déjà en C, mais je tenais juste à vous informer que cette technique fonctionnait toujours avec le type bool ;)

Les déclarations de variables

En C, les variables devaient être déclarées (= créées) au début des fonctions. Vous avez vu cela dans le chapitre sur les variables au tout début du cours ;)

Vous deviez donc faire toutes vos déclarations avant de commencer les instructions :

Code : C
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
void maFonction()
{
    // D'abord on déclare les variables
    double prixOrigine = 0.0;
    double prixAchat = 0.0;
    double difference = 0.0;
    FILE* fichier = NULL;

    // Ensuite on peut exécuter des instructions, des appels de fonction, etc.
    fichier = fopen("exemple.txt", "r");

    if (fichier != NULL)
    {
        fonction();

        fscanf(fichier, "%lf", &prixOrigine);
        fscanf(fichier, "%lf", &prixAchat);
        
        difference = prixAchat - prixOrigine;
        // etc.
    }
}


La nouveauté en C++, c'est que l'on peut désormais déclarer des variables n'importe où dans une fonction. C'est plus pratique lorsqu'on programme, ça nous évite d'avoir à remonter au début de la fonction si on n'a besoin d'une variable qu'à un moment de la fonction. Cela peut aussi améliorer la lisibilité du code surtout dans de grosses fonctions.

On pourrait donc écrire le code précédent comme ceci en C++ :

Code : C++
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
void maFonction()
{
    FILE* fichier = NULL;
    fichier = fopen("exemple.txt", "r");

    if (fichier != NULL)
    {
        fonction();

        double prixOrigine = 0.0; // Déclaration au milieu
        fscanf(fichier, "%lf", &prixOrigine);
        
        double prixAchat = 0.0; // Autre déclaration au milieu
        fscanf(fichier, "%lf", &prixAchat);
        
        double difference = prixAchat - prixOrigine; // Encore autre déclaration au milieu
        // etc.
    }
}


Avec une version récente du langage C, il est aussi possible de déclarer une variable en plein milieu d'une fonction. Cependant, les programmeurs C préfèrent en général continuer à déclarer leurs variables au début des fonctions.


Précision importante : les variables ainsi créées sont locales aux blocs où elles ont été déclarées. Je m'explique.
On dit que les accolades { et } délimitent des blocs. Dans le code ci-dessus, vous devriez en voir deux : la fonction et le bloc if. Comme la variable prixAchat a été déclarée dans le bloc if, elle sera supprimée à la fin du bloc if. Si elle avait été déclarée au début de la fonction en revanche, elle aurait été accessible dans toute la fonction.

Voilà, c'est assez simple à comprendre mais il faut le savoir ! La variable est détruite à la fin du bloc dans lequel elle a été déclarée.

Code : C++
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
void maFonction()
{
    FILE* fichier = NULL;
    fichier = fopen("exemple.txt", "r");

    if (fichier != NULL)
    {
        fonction();

        double prixOrigine = 0.0; // Création de prixOrigine
        fscanf(fichier, "%lf", &prixOrigine);
        
        double prixAchat = 0.0; // Création de prixAchat
        fscanf(fichier, "%lf", &prixAchat);
        
        double difference = prixAchat - prixOrigine; // Création de difference
    } // Destruction automatique de prixOrigine, prixAchat et difference
} // Destruction automatique de fichier


Déclaration dans une boucle



Dans le même ordre d'idée, il y a une nouveauté vraiment très pratique (comprenez : je m'en sers tout le temps :p ). On peut déclarer une variable directement dans une instruction for.

Prenons un exemple. Vous codez votre programme, tout va bien. Puis à un moment, pour une raison ou une autre, vous avez besoin de faire une boucle qui se répète 10 fois. Vous allez sûrement faire un for. Mais pour boucler 10 fois, vous aurez besoin d'une variable de boucle qui va retenir le nombre de tours de boucle (quand on n'est pas inspiré on appelle en général cette variable i ).

En C, c'est un peu embêtant parce qu'il faut remonter au début de la fonction pour rajouter la déclaration de la variable. En plus, on ne sait pas trop quand elle sera utilisée en lisant la déclaration :

Code : C
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
void maFonction()
{
    int i = 0;

    /* Plein de code
    ....
    ....
    */

    for (i = 0 ; i < 10 ; i++)
    {

    }
}


La nouveauté en C++, c'est que vous pouvez déclarer votre variable i directement dans l'instruction for. Elle sera détruite à la fin de la boucle, quand vous n'en aurez plus besoin.
Avantages : vous n'avez pas à remonter au début de la fonction pour déclarer la variable, et celle-ci est automatiquement détruite à la fin de la boucle. Pas d'utilisation inutile de la mémoire.

Le code C++ ressemblera donc à cela :

Code : C++
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
void maFonction()
{
    /* Plein de code
    ....
    ....
    */

    for (int i = 0 ; i < 10 ; i++) // Déclaration de i
    {

    } // Destruction automatique de i
}


Ca n'a l'air de rien, mais je vous assure qu'en pratique quand on programme, ça c'est vraiment génial :D
Vous me verrez donc le faire la plupart du temps dans la suite du cours.

Les allocations dynamiques

Si je vous dis "malloc" et "free", ça vous rappelle de joyeux souvenirs non ? ^^

Code : C
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
int main()
{
    int *variable = NULL;

    variable = malloc(sizeof(int)); // Allocation de mémoire

    free(variable); // Libération de mémoire

    return 0;
}


L'allocation dynamique est une technique qui permet de gérer vous-même l'allocation de mémoire pour vos variables. C'est notamment très pratique dans le cas de l'allocation de tableaux dont on ne connaît pas la taille avant compilation (revoyez le chapitre sur l'allocation dynamique au besoin !).

En C++, les allocations dynamiques existent toujours et on en fait toujours. D'ailleurs, les fonctions malloc et free sont toujours utilisables. Cependant, le C++ dispose de nouveaux opérateurs spécialisés dans les allocations dynamiques : new et delete.

new et delete sont des opérateurs, des mots-clé du langage C++. Contrairement à malloc et free, ce ne sont pas des fonctions.
new et delete font en fait eux-mêmes appel aux fonctions malloc et free (on n'a pas réinventé la roue). Cependant, ils font aussi des tests et des initialisations supplémentaires, ce qui fait qu'on préfèrera toujours utiliser new et delete au lieu de malloc et free. Ils sont plus adaptés en C++.


Allocation dynamique d'une variable



new et delete ne s'utilisent pas exactement de la même manière que malloc et free.
On va dans un premier temps apprendre à s'en servir pour allouer une variable simple, puis on verra ensuite le cas de l'allocation de tableaux.

On souhaite donc allouer dynamiquement une variable (de type int par exemple).
En C++, on va d'abord devoir créer le pointeur et l'initialiser à NULL, ça on n'y coupe pas :

Code : C++
1
int *variable = NULL;


Allocation de mémoire



L'allocation de mémoire avec new se fait comme ceci :

Code : C++
1
variable = new int; // Allocation dynamique


Comparé à la "version C", il n'y a pas photo :D
On n'a plus besoin d'utiliser l'opérateur sizeof() du C. Ici, on indique juste le type de variable à créer.

Libération de mémoire



Lorsque vous avez fini d'utiliser votre variable et que vous n'en avez plus besoin, vous devez la libérer avec l'opérateur delete. Ultra-simple :

Code : C++
1
delete variable; // Libération de mémoire


new et delete étant des opérateurs, et non des fonctions (désolé d'insister :p ), on ne met pas de parenthèses.


Résumé



En résumé, voici à quoi ressemble un code d'allocation / libération de mémoire en C++ :

Code : C++
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
int main()
{
    int *variable = NULL;

    variable = new int; // Allocation de mémoire

    delete variable; // Libération de mémoire

    return 0;
}


Allocation dynamique d'un tableau



Si on veut allouer un tableau, l'opération est là encore très simple. On n'a plus besoin de faire un calcul du type 20 * sizeof(int) comme on devait le faire en C.

On commence par créer le pointeur :

Code : C++
1
int *tableau = NULL;


Allocation de mémoire



Ensuite, l'allocation se fait comme ceci :

Code : C++
1
tableau = new int[20]; // Allocation de mémoire (20 cases)


Dans ce cas, un tableau de 20 cases sera alloué. Bien entendu, il est aussi possible de remplacer ce nombre par une variable :

Code : C++
1
tableau = new int[taille]; // Allocation de mémoire ("taille" cases)


La longueur du tableau sera définie par la valeur de la variable taille.

Libération de mémoire



Lorsque vous n'avez plus besoin du tableau, vous devez le libérer... avec cette fois l'opérateur delete[] pour bien préciser qu'il s'agit d'un tableau. Vous n'avez pas besoin de préciser la taille entre crochets, mais n'oubliez pas ces crochets ils sont importants.

Code : C++
1
delete[] tableau; // Libération de mémoire


Résumé



Code : C++
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
int main()
{
    int *tableau = NULL;

    tableau = new int[20]; // Allocation de mémoire (tableau)

    delete[] tableau; // Libération de mémoire (tableau)

    return 0;
}


Il y a donc en tout 4 opérateurs :

Le typedef automatique

Vous souvenez-vous du chapitre sur les structures et énumérations ? :)
On y avait appris à créer nos propres types de variables. On avait notamment utilisé l'exemple d'une structure nommée Coordonnees :

Code : C
1
2
3
4
5
struct Coordonnees
{
    int x;
    int y;
};


Le problème des structures en C, c'est qu'il fallait placer le mot-clé struct au début de chaque déclaration d'une variable de type personnalisé :

Code : C
1
struct Coordonnees point;


Pour éviter d'avoir à répéter ce mot à chaque déclaration, on avait découvert l'instruction typedef qu'on utilisait comme ceci avant la définition de notre structure :

Code : C
1
2
3
4
5
6
7
typedef struct Coordonnees Coordonnees; // typedef permet d'éviter d'avoir à taper "struct" à chaque déclaration

struct Coordonnees
{
    int x;
    int y;
};


Du coup, on pouvait déclarer une variable sans avoir à écrire struct devant :

Code : C
1
Coordonnees point; // Le mot-clé struct est inutile grâce au typedef



La nouveauté



En C++, qu'on se rassure, les structures existent toujours (il y a même encore mieux, mais n'anticipons pas ;) ).

La nouveauté du C++, c'est que le typedef est désormais automatique. A chaque fois que l'on déclare une structure (ou une énumération), un typedef est réalisé automatiquement par le compilateur. On peut donc n'écrire que l'instruction de déclaration de la structure :

Code : C++
1
2
3
4
5
6
7
// Le typedef est réalisé automatiquement par le compilateur, pas besoin de l'écrire

struct Coordonnees
{
    int x;
    int y;
};


Grâce à cela, le mot-clé struct devient totalement inutile lors d'une déclaration de variable :

Code : C++
1
Coordonnees point; // Le mot-clé struct est inutile grâce au typedef automatique

Les références

Nous arrivons maintenant au point le plus important (et délicat) de ce chapitre. Ouvrez grandes vos oreilles (ou plutôt vos yeux :-° ).

Le C++ introduit un nouveau concept : les références. Une référence est un synonyme d'une autre variable. On verra ce que ça veut dire un peu plus loin ;)
Vous allez voir que les références ressemblent beaucoup aux pointeurs. Elles ont en effet été créées pour simplifier l'utilisation des pointeurs. Attention toutefois : je vous préviens qu'au début vous risquez de confondre les références avec les pointeurs (c'est assez perturbant quand on voit ça la première fois j'avoue :p ).


Les références à l'intérieur d'une fonction



Pour créer une référence, on doit utiliser le symbole & dans la déclaration :

Code : C++
1
int &referenceSurAge;


Attention à ne pas confondre !
Dans une déclaration, le symbole & signifie "Je veux créer une référence" (c'est ce qu'on découvre maintenant). Partout ailleurs, le symbole & signifie "Je veux obtenir l'adresse de cette variable" (ça on l'avait déjà vu).
On confond facilement quand on débute. Il faut dire que les programmeurs n'ont pas été très malins en réutilisant le symbole & ici, y'a rien de tel pour confondre :-°
Quand vous voyez un & désormais, vérifiez s'il se trouve dans une déclaration : si c'est dans une déclaration, c'est qu'on cherche à créer une référence, sinon c'est qu'on demande à obtenir l'adresse de la variable.


Bon, on a créé une référence. Et alors ?
Et alors si vous compilez le code ci-dessus, le compilateur va vous insulter poliment :

Citation : Compilateur C++
error: 'referenceSurAge' declared as reference but not initialized


Si vous lisez l'anglais (et si vous ne le lisez pas vous devriez), vous avez compris le problème : le compilateur veut qu'on initialise immédiatement la référence.
Et ça c'est très important : une référence doit être immédiatement initialisée dès le début, contrairement aux pointeurs. Et ce n'est pas tout : une fois initialisée, la référence ne pourra plus changer !

Il y a donc deux règles que j'aimerais que vous reteniez par coeur :



Initialisation d'une référence



On va donc initialiser notre référence.
Comme je vous l'ai dit un peu plus tôt, une référence est un synonyme d'une autre variable. Cela veut donc dire qu'il faut créer une autre variable pour y trouver un minimum d'intérêt :p

Allez hop, il est l'heure de ressortir la bonne vieille variable qui a fait ses preuves : la variable... age !
(le premier qui ose dire que je fais des cours pas originaux il va tâter de mon sabre :pirate: )

Code : C++
1
2
int age = 21; // Déclaration de la variable age (rien de nouveau)
int &referenceSurAge = age; // Déclaration et initialisation d'une référence sur la variable age


Pour initialiser une référence, vous avez juste besoin d'écrire le nom de la variable dont elle sera le synonyme. Pas besoin d'écrire &age comme on le faisait avant avec les pointeurs.

Je vous avais prévenu, vous risquez de confondre avec les pointeurs.
Je vous ferai un résumé comparatif pointeurs / références un peu plus loin pour que vous puissiez bien les comparer.


Utilisation de la référence



Bon, maintenant notre référence est créée. On a un synonyme de la variable age.
Comment on s'en sert concrètement ? Exactement comme la variable age ! Pas besoin de mettre une étoile * devant pour dire qu'on veut obtenir la valeur. Les références permettent, vous allez le voir, de simplifier l'écriture de nos programmes pour éviter au maximum les erreurs (un oubli d'une étoile est si vite arrivé !).

Regardez ce petit programme complet qui affiche la variable age, la modifie, et la réaffiche, le tout en passant par une référence :

Code : C++
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
int main()
{
    int age = 21;
    int &referenceSurAge = age;

    cout << referenceSurAge << endl;
    cout << age << endl;

    referenceSurAge = 40;

    cout << referenceSurAge << endl;
    cout << age << endl;


    return 0;
}


Résultat :

Code : Console
21

21

40

40


Comme vous pouvez le voir, une référence s'utilise exactement comme la variable d'origine. C'est le compilateur qui fait la "conversion" et qui sait qu'il doit affecter la variable "age" lorsqu'on travaille avec la référence.

Comparatif pointeur / référence



En C++, les pointeurs existent toujours. Les références sont juste une alternative aux pointeurs. Elles ont surtout l'avantage d'être plus simples à utiliser, mais elles ne peuvent pas les remplacer complètement.
Pourquoi ? On l'a vu : une référence ne peut pas faire référence à une nouvelle variable une fois qu'elle a été initialisée. Un pointeur, lui, peut toujours pointer vers une nouvelle variable au cours de l'exécution du programme.

Dans certains langages récents, comme le Java, les pointeurs ont complètement disparu. On n'utilise plus que des références, ce qui limite beaucoup les risques d'erreur et simplifie les programmes. La différence, c'est qu'en Java on peut modifier les références en cours de route, contrairement au C++ ;)


Il est très courant de confondre les pointeurs et les références lorsqu'on débute (si ça peut vous rassurer, moi aussi j'ai pas mal confondu au début). Je vais donc vous donner 2 codes source : le premier utilise les pointeurs, le second les références. Si à un moment vous avez un doute et que vous vous mettez à confondre pointeurs et références, servez-vous de l'exemple ci-dessous pour vous assurer que vous faites les choses correctement :

-------- Code d'exemple avec un pointeur ---------------- Code d'exemple avec une référence --------
Code : C++
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
int main()
{
    int age = 21;
    int *pointeurSurAge = &age;

    cout << *pointeurSurAge;

    *pointeurSurAge  = 40;

    cout << *pointeurSurAge;


    return 0;
}
Code : C++
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
int main()
{
    int age = 21;
    int &referenceSurAge = age;

    cout << referenceSurAge;

    referenceSurAge  = 40;

    cout << referenceSurAge;


    return 0;
}


Voilà, j'espère que ce comparatif vous permettra d'y voir plus clair :)
Ce qu'il faut retenir dans l'histoire, c'est que les références sont là pour simplifier l'écriture du code source. Comme on n'a plus besoin d'utiliser le symbole * à chaque fois qu'on veut accéder à la variable age, on minimise les risques d'erreur dans nos programmes.

Les références vers des structures



Si vous faites une référence vers une structure, il faudra utiliser le symbole point "." et non le symbole flèche "->" lorsque vous voulez accéder à un élément d'une structure.

Code : C++
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
struct Coordonnees
{
    int x;
    int y;
};

int main()
{
    Coordonnees point;
    Coordonnees &referenceSurPoint = point;

    referenceSurPoint.x = 10;
    referenceSurPoint.y = 5;

    cout << "x : " << referenceSurPoint.x << endl;
    cout << "y : " << referenceSurPoint.y << endl;


    return 0;
}


Code : Console
x : 10

y : 5


Une fois de plus, vous voyez qu'une référence s'utilise exactement comme une variable ;)

Les références lors d'un appel de fonction



Les codes qu'on a vus jusqu'ici n'étaient pas très utiles. En pratique, on n'est pas suffisamment maso pour créer des références juste "pour le plaisir" si elles ne sont pas indispensables.

En fait, comme pour les pointeurs, les références révèlent toute leur utilité lorsqu'on appelle une fonction.

Voyons voir ça dans un exemple :

Code : C++
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
struct Coordonnees
{
    int x;
    int y;
};

void remiseAZero(Coordonnees &pointAModifier);

int main()
{
    Coordonnees point;

    remiseAZero(point); // Pas besoin d'indiquer l'adresse de point avec un & lors de l'appel

    return 0;
}

void remiseAZero(Coordonnees &pointAModifier) // La fonction indique qu'elle récupère une référence
{
    // La référence s'utilise exactement comme une variable
    // On utilise donc des point