Accueil > Dossiers > C++

C++ (tutorial 7)

Indications pour ceux qui veulent lire vite...

Les titres principaux des parties sont centrés en gras et soulignés. Les sous-parties sont alignées dans la gauche, en gras-italique et marron. Les récapitulatifs sont en bleu. Les codes C++ sont écrit en type "codé" et gras.


SOMMAIRE DU TUTORIAL 7

I. Les fonctions
1.1 Création d'une fonction
1.2 Utilisation d'une fonction
1.3 La Portée des variables
1.4 Gestion des fonctions et variables globales
1.5 Abus des portées de variable
1.6 Etude de programme

II. Les classes
2.1 Création de classe
2.2 Fonctions de classe
2.3 Constructeur
2.4 Destructeur



I. Les fonctions >> vers le sommaire

1.1 Création d'une fonction >> vers le sommaire

Actuellement, nous avons déjà utilisé une fonction importante, main(), et d'autres fonctions comme sqrt(). Revenons donc sur leur fonctionnement. Les fonctions permettent de donner une meilleure structure au programme : par exemple, au lieu de recopier le code calculant la racine carré, on l'a écrit une fois dans tout le programme, et on y fait simplement appel par une fonction. Si vous ne voyez pas où est écrit ce code de calcul de la racine carré, c'est qu'il se trouve simplement dans un autre endroit du code, en l'occurence la bibliothèque Math.h.

On m'a déjà dit : "oui, mais une fonction renvoit une valeur, et la fonction main() ne renvoit rien !". La fonction main() est très spéciale : elle est la base du programme. Le programme commence par la fonction main(), et lorsque celle-ci se finit, il se finit. Cependant, vous avez remarqué qu'à chaque compilation de programme nous avions un warning : "warning C4508: 'main' : function should return a value". Ainsi, si dans nos codes précédents la fonction main() ne renvoyait rien, c'était une erreur ! Nous ne l'avons pas corrigé car j'attendais de pouvoir la traiter dans un tutorial sur les fonctions.

Ouvrez le projet base et nettoyez-le pour qu'il soit comme ci-dessous :

Le type d'information renvoyé par une fonction est spécifié avant le nom de celle-ci. Le type peut-être un de ceux que vous connaissez (double, int, char, bool...) mais on peut aussi spécifier que la fonction renvoit "rien" par le type void. Voici un exemple :
#include <iostream.h>

void main()
{
}

Ce code est tout à fait correct, et aucun warning n'apparaîtra. void signifie que la fonction ne renvoit pas d'information, et c'est exactement ce qu'elle fait, donc tout va. Retenez bien que la fonction doit impérativement renvoyé une valeur du type prévu dans sa déclaration. Si le type est void, alors elle devra ne rien renvoyer, si le type est int elle devra renvoyer une valeur qui appartienne à l'ensemble des int, etc. etc. Par exemple :
#include <iostream.h>

int main()
{
return 0;
}

L'opérateur qui permet de retourner une valeur est l'opérateur return. Maintenant que le principal est dit, nous allons créer une nouvelle fonction. Toute autre fonction que main() doit être déclaré deux fois : une fois en début de code pour donner ses renseignements (par exemple, son type : s'il s'agit d'une fonction qui retournera un int, un char...) et ensuite, après la fonction main(), avec son contenu. Voici un exemple :

On déclare une première fois la fonction exemple en début de code, en mettant bien le ; à la fin, et on y fait appel dans la suite du code, sans mettre le;. La seule différence entre les deux déclarations réside donc dans ces fameux ;. En compilant le programme, vous obtiendrez le résultat suivant :

Récapitulatif sur la création d'une fonction :
- Le type de la fonction est précisé avant son nom, par exemple void exemple() ou int exemple()
- La fonction doit absolument renvoyer, a l'aide de return, une information du type prévu. Par exemple, la fonction int exemple() aura, à la fin, return 0. Le cas particulier est celui de void exemple() où on ne mettra rien.
- Toutes les fonctions, sauf la fonction main(), doivent être déclarées deux fois. La première fois au début du code avec un ; à la fin, et la seconde fois avec le contenu de la fonction, après main(), sans le ;.

1.2 Utilisation d'une fonction >> vers le sommaire

Il y a deux notions clés avec les informations d'une fonction : transmettre et recevoir. Pour recevoir les résultats d'une fonction, voici un exemple :

La variable a est de type int et l'information renvoyée par la fonction exemple() est de type int, donc a peut stocker cette information. Il faut que la variable de stockage ait le même type que l'information à stocker, c'est assez évident d'ailleurs. Voici ce que donne l'éxécution du programme :

Si la variable de stockage n'a pas le même type que l'information à stocker, soit cela donne un bug (mais c'est rare), soit le compilateur vous averti qu'il y a une perte d'information, soit le résultat est correct en soi mais peut-être pas celui attendu.

Pour envoyer une information à une fonction, il faut qu'une variable soit prévue pour réceptionner l'information. Les variables associées à la fonction se trouvent entre les parenthèses, voici un exemple :

On veut pouvoir envoyer une information de type int à la fonction. Dans sa déclaration, on crée donc une variable de type int qui recevra l'information. Devinez quelle sera la valeur finale de a ? A l'origine a vaut 5, puis la fonction exemple lui soustrait 2 et renvoit la nouvelle valeur : a vaut donc 3 à la fin du programme.

Récapitulatif sur l'utilisation d'une fonction :
- Pour recevoir la valeur d'une fonction, on l'attribue simplement à une variable : a=exemple(). Il est fortement recommandé que la variable qui reçoit l'information soit du même type que l'information renvoyée par la fonction. Par exemple int exemple() renvoit une information de type int, la variable devrait être de type int.
- Pour envoyer une information à une fonction, il faut qu'une variable permettant de recevoir l'information soit associée à cette fonction. Par exemple, pour a=exemple(5); il faut que la fonction exemple puisse recevoir un int, donc on rajoute dans les deux déclarations de la fonction int b qui recevra l'information : int exemple(int b).

1.3 La portée des variables >> vers le sommaire

Examinez le code suivant :

A première vue, il n'y a rien de spécial. Pas d'erreur de syntaxe, les fonctions renvoient ce qu'il faut et sont bien déclarées, la variable a est bien déclarée. Alors ? Compilez le programme, le compilateur vous affichera l'erreur suivante : "error C2065: 'a' : undeclared identifier"

Pourtant, vous avez bien déclaré la variable a. Le problème est, en réalité, que les variables ont certaines portées : elles fonctionnent jusqu'à certains endroits. Quand on crée une variable dans un bout du code, cette variable existe de l'endroit où elle a été crée à la fermeture du bloc auquel elle appartient par un }. Voici un exemple :
int a(0);
if(a==0)
{
int b;
}
b=2;

On a crée la variable b dans un bloc de code (note : un bloc est l'ensemble commençant à un { et s'arrêtant au } le plus proche) qu'on a refermé, la variable a donc cessé d'exister et lui attribuer une valeur de 2 ne rime plus à rien car elle n'existe plus. Pour revenir à notre problème avec le fonction exemple(), c'est exactement pareil : la variable a existe dans la fonction main() et non dans la fonction exemple(), par conséquent cette dernière ne peut plus y accéder.

Pour que plusieurs fonctions puissent accéder à une même variable, il faut que celle-ci soit une variable globale c'est-à-dire qu'on la déclare en début de code. Voici un code fonctionnel :

Récapitulatif sur la portée des variables :
- Une variable crée dans un bout du code, appelée variable "locale", est utilisable à partir de l'endroit où elle a été crée jusqu'au } le plus proche.
- Une variable crée au début du code, en dehors des fonctions, est appelée variable "globale". Elle est utilisable dans tout le programme.

1.4 Gestion des fonctions et variables globales >> vers le sommaire

Pour gérer les variables globales et les fonctions, utilisez l'onglet ClassView
En cliquant sur cet onglet, vous verrez les variables globales et fonctions :

Vous pourrez ensuite accéder à des fonctionnalités plus avancées. Faites un clic droit sur la fonction exemple et choisissez "Called by" pour voir quels sont les endroits du code où elle a été mentionnée :

Il est très probable que la fonctionalité "Called By" ne soit pas accessible par défaut, auquel cas vous devrez "Yes" au message suivant :

Après, vous aurez droit à la fenêtre suivant :

La seconde définition de la fonction exemple se trouve en ligne 14, sa première définition en ligne 2, et on y fait appel en ligne 9. En double cliquant sur une des lignes indiquées, vous serez soit amené à la ligne en question (pour les Definitions), soit le nom de la fonction sera surligné (pour les References). S'il arrive que vous ayez des problèmes dans un code de grande taille, ce genre d'aide au repérage se montrera utile.

Récapitulatif sur la gestion des fonctions et variables globales :
- En utilisant l'onglet ClassView vous accéderez aux fonctions et variables globales. A partir d'un clic droit, vous pourrez obtenir, entre autre, les propriétés et les appels ("Calls", "Called by"...).
- Dans la fenêtre d'appels (Callers Graph), si vous faites un double-clic sur une ligne de Definition, vous serez emmené à la ligne en question ; si vous faites un double-clic sur une ligne de Reference, la nom de l'objet (nom de la variable ou nom de la fonction) sera surligné.

1.5 Abus des portées de variable >> vers le sommaire

Il est possible de créer plusieurs variables avec le même nom si on fait cette création dans des blocs différents. Le code suivant est valide :

La seconde variable a masque la première et, lorsque la portée de la seconde est finie, on retrouve la première. On explique souvent ce genre de chose dans les livres, mais attention : si vous vous risquez à faire cela, votre programme va devenir rapidement incompréhensible. Il existe un opérateur de résolution de portée pour atteindre la variable globale si elle est masquée. Imaginez ce que donne le code suivant à l'affichage :

Entrons directement dans la fonction main() D'abord, on crée un a qui vaut 3 puis on affiche le dernier a en date, on va donc afficher 3. Ensuite, si le dernier a en date vaut 3 (c'est le cas), on affiche un a qui vaut 7, donc on va afficher 7. La condition else ne se réalisera pas alors car la condition if s'est réalisée. La seconde condition if utilise l'opérateur de résolution de portée, les :: désignent la variable globale ; puisque la variable globale vaut 10, alors on affichera 10. Et ainsi :

Récapitulatif sur les abus des portées de variable :
- On peut créer plusieurs variables avec le même nom si la création s'effectue dans des blocs différents, mais c'est très fortement déconseillé.
- Avec l'opérateur de résolution de portée :: on fait appel à la variable globale.

1.6 Etude de programme >> vers le sommaire

Si vous souhaitez étudier plus particulièrement les fonctions et les calculs, voici un programme sympathique à étudier : Aqualonne Math v1.05. Réalisé en deux jours, il contient quelques calculs simples de math et vous pourrez en reprendre du code, ou l'améliorer (si vous repérez des bugs), au besoin. Ses fonctionnalités :
- Calcul de l'expression (a+b)².
- Calcul des coordonnés d'un vecteur AB à partir des coordonnés des points A et B.
- Calcul d'un nombre à une puissance.
- Calcul des coordonnés du milieu d'un vecteur à partir des coordonnés du vecteur.
- Détermination d'une fonction à partir de deux valeurs de x et des deux images.
- Calcul de l'aire d'un carré, d'un rectangle et d'un cercle.
- Calcul du périmetre d'un carré, d'un rectangle, d'un parellogramme, d'un triangle, d'un trapeze et d'un cercle.
- Vérification si le triangle est rectangle, isocele ou equilateral.
- Signale si la fonction est une fonction inverse.
- Signale si la fonction peut etre une fonction caree ou cube.
- Signale si la fonction peut etre un enchainement inverse et cube ou inverse et carré.
- Calcul de l'aire d'un parallelepipede rectangle, d'une sphere et d'un cylindre droit.
- Calcul du volume d'un parallelepipede rectangle, d'une sphere et d'un cylindre droit.
- Si le triangle est rectangle, on donne cosinus/sinus/tangente de tous les angles.

A l'intérieur du pack, vous trouverez :
- Les news sur l'évolution du programme.
- Codes sources des version 1.00, 1.01, 1.02, 1.03, 1.04 et 1.05.
- Exécutable du programme.

Je met aussi à votre disposition les bribes d'un programme devant servir à comparer la puissance des voitures par rapport aux distances qu'elles parcouraient selon des distances de recul : Comparateur.



II. Les classes >> vers le sommaire

2.1 Création d'une classe >> vers le sommaire

Dans le tutorial précédent, nous avons crée des structures. Une fois la structure créée, il était possible de fabriquer un objet issu de cette structure : les classes permettent de faire la même chose. Voici un code de structure :
struct human{
int matricule;
int code_postal;
short age;
bool sexe;
};

Et voici un code de classe :

On remplace struct par class simplement. Pour la suite, c'est une question de préférence syntaxique : on commence toujours le nom d'une classe par C et le nom des membres d'une classe par m_. On remarquera aussi le public:. Dans les classes il y a trois degrés de protection des données, et public: est le degré le plus bas, c'est des données "publiques", accessibles à tous.

Pour la suite, on crée un objet sur le modèle de cette classe comme pour les structures, par exemple :
CHuman Humain;

Ensuite, on accédera au contenu de l'objet en tapant son nom et le nom de la variable à accéder, séparée par un point. On remarquera que, une fois le point tapé, une liste s'affiche pour y choisir la variable, il est préférable de l'utiliser plutôt que de taper le nom soit-même afin d'éviter les risques de fautes de frappes :

Voici donc un code simple utilisant une classe :

Récapitulatif sur la création de classe :
- La création d'une classe est similaire à celle d'une structure. On crée une classe de la manière suivante :
class CNomclasse
{
public:
int m_contenu;
};

- Dans la syntaxe des classes, le nom d'une classe commence toujours par C et celui de ses membres par m_.
- Les classes protègent leur contenu à divers degrés. public: est le degré de protection le plus faible.
- Pour créer un objet, on tape le nom de la classe suivi du nom de l'objet à créer. Par exemple CNomclasse Nomobjet;
- Pour accéder au contenu d'un objet on marque son nom puis un point et le nom de la variable, par exemple Nomobjet.m_contenu

2.2 Fonctions de classe >> vers le sommaire

Les classes peuvent posséder leurs propres fonctions. Celles-ci doivent être déclarées dans la classe, comme suit :
class CTriangle
{
public:
int m_a;
int m_b;
int m_c;
bool rectangle(int a,int b,int c);
};

Le contenu de la fonction doit suivre la création de la classe. La seule différence par rapport à précédemment est que le nom de la fonction doit être précédé du nom de la classe suivi de ::, par exemple :
bool CTriangle::rectangle(int a, int b, int c)
{
if (a*a+b*b==c*c || a*a+c*c==b*b || c*c+b*b==a*a)
{
// triangle rectangle
return true;
}
return false;
}

Pour faire appel à la fonction, c'est comme si on faisait appel à une des variables de la classe. Soit Untriangle l'objet crée sur le modèle de cette classe, le code pourrait être :
void main()
{
if(Untriangle.rectangle(4,5,6)==true)
{
cout << "Triangle rectangle\n";
}
else
{
cout << "Triangle non rectangle\n";
}
}

Récapitulatif sur les fonctions de classe :
- Elles doivent être déclarées dans la classe, comme les variables.
- Leur seconde déclaration est celle que nous avons apprise au début du tutorial, mais avant le nom de la fonction on marque celui de la classe suivi de ::
- On fait appel à une fonction de classe en marquant le nom de l'objet crée sur son modèle suivi d'un point et du nom de la fonction, avec les arguments (c'est-à-dire les valeurs) entre parenthèse s'il y a lieu.

2.3 Constructeur >> vers le sommaire

Par défaut, le compilateur fournit un constructeur standard quand nous créons un objet. Pour vous donner une idée de ce qu'est un constructeur, disons que c'est ce qui donne les valeurs des variables de l'objet que nous créons. Le constructeur est une fonction se trouvant en totalité dans la classe et n'ayant pas besoin d'une première déclaration. Voici comment est le constructeur par défaut :

La fonction du constructeur doit porter impérativement le même nom que la classe. En éxécutant le programme vous remarquerez que, même si vous n'avez pas demandé explicitement l'utilisation du constructeur, le message "Constructeur par defaut." va s'afficher. En effet, toute création d'un objet passe par l'utilisation du constructeur. Il est parfois intéressant d'atribuer par défaut des valeurs aux membres de la classe. Vous pouvez utiliser un constructeur qui attribue automatiquement les valeurs :

Ici, le constructeur donne automatiquement les valeurs 1, 2 et 3 aux variables m_a, m_b et m_c. Vous pouvez aussi utiliser un constructeur qui vous laisse plus de libertés, en s'en servant comme une fonction. Voici un exemple :

Il vous suffit de spécifier les valeurs lorsque vous créez l'objet, en les indiquant entre parenthèses.

Récapitulatif sur le constructeur :
- Il est utilisé à chaque fois qu'un objet est crée sur le modèle d'une classe.
- Il s'agit d'une fonction portant le même nom que la classe et se trouvant en une seule fois dans le programme, contenu dans la classe. Si aucun constructeur n'est spécifié, le compilateur en rajoute un par défaut, par exemple pour la classe CTriangle :
CTriangle()
{
}

- Le but du constructeur est de donner des valeurs aux variables de classe. On peut utiliser un constructeur statique qui donne automatiquement des valeurs, par exemple :
class CTriangle
{
public:
int m_a;
CTriangle()
{
m_a=1;
}

- On peut créer un constructeur qui sera véritablement utilisé en fonction pour plus de liberté, par exemple :
class CTriangle
{
public:
int m_a;
CTriangle(int a)
{
m_a=a;
}
};
CTriangle Untriangle(1);

2.4 Destructeur >> vers le sommaire

De même qu'il existe un constructeur, il existe un destructeur. Voici un code pour voir le destructeur par défaut :

En éxécutant le code, vous constaterez que le message "Utilisation du destructeur" s'affiche ; il gère la mémoire.

Récapitulatif sur le destructeur :
- Il s'agit d'une fonction portant le même nom que la classe précédé d'un ~ et se trouvant en une seule fois dans le programme, contenu dans la classe.
- Si aucun constructeur n'est spécifié, le compilateur en rajoute un par défaut, par exemple pour la classe CTriangle :
~CTriangle()
{
}


Nous sommes peut-être allé un peu rapidement sur les classes, qui sont une notion essentielle du langage C++. Je vous recommandes vivement de consulter le programme Matrix pour les étudier davantage. Ce programme est une "console" c'est-à-dire que vous lui donnez et qu'il gère des commandes. Il ne gère que trois commandes : "setmatrix", "setpower" et ";-)matrix". Ce programme a été inspiré par le défilement des caractères célèbre du film Matrix : "setmatrix" règle le nombre de chaînes de caractères qui défileront (plus le nombre est élevé, plus le défilement sera important), "setpower" règle le nombre de chaînes de caractères qui défileront mais à l'envers (plus le nombre est élevé, moins le défilement sera important), enfin ";-)matrix" fait défiler les chaînes.


Vous êtes maintenant formé aux bases du C++. Si vous souhaitez aller plus loin, je propose des tutoriaux pour la programmation graphique windows en C++.

Cliquez ici pour aller au premier tutorial sur la programmation graphique windows en C++ >>