I - 4. Les fonctions
A. Découper son programme
a. Où sont situées les instructions ?
Pour la première fois depuis le début de ce cours, nous allons regarder un code assembleur complet. Cependant, nous n'allons pas voir un code utilisant des boucles, conditions... Nous allons regarder un code très simple : le premier code que nous avons vu, simplifié :
Pour des raisons de santé mentale, je ne vais pas vous arroser d'assembleur pour continuer à vous expliquer les fonctions. Je vais donc vous montrer comment utiliser une fonction en pseudo-code. Voici une façon de faire :
Une chose, très importante, qu'il faut comprendre avec les fonctions, est comment les utiliser. En effet, les fonctions ne peuvent pas s'exécuter toutes seules. En effet, ce que nous avons vu plus tôt (en assembleur et en pseudo-code) ne sont que les définitions des fonctions "main", mais pas leur utilisation. Pour les utiliser, et donc exécuter les instructions dans la définition, il faut utiliser une instruction nommée "un appel de fonction". On dit qu'on appelle une fonction. En assembleur, un appel de fonction se fait comme ça :
b. Les paramètres de fonctions
Quand on dit le mot fonction, on peut penser aux fonctions mathématiques. Cependant, une fonction mathématique nécessite une donnée, souvent "x", pour générer un résultat. En informatique, on peut le faire aussi, et passer des données à une fonction pour que son comportement dépende des données. Ces données passées à la fonction sont appelées des "paramètres de fonction". Il s'agit de variables (ou de constantes), exactement comme on les a vu dans le cours des variables. Elles doivent être ajoutées entre les parenthèses, après le nom de la fonction. Voici un exemple d'utilisation d'une fonction avec des paramètres :
Cependant, dans certains cas, une valeur de paramètre peut être tellement redondante que devoir la mettre à chaque appel de la fonction peut être ennuyant. Heuresement, vous pouvez spécifier une valeur par défaut à un paramètre. Une valeur par défaut d'un paramètre est utilisé si le paramètre n'a pas de valeur lors de l'appel de la fonction. Pour cela, il suffit simple de rajouter "= valeur" après la déclaration entre parenthèse, comme ça :
La façon dont on passe les variables a des conséquences sur la façon dont elles seront utilisées dans la fonction. En effet, si on passe une variable comme dans l'exemple au-dessus, le contenu de la variable passée est copié dans le paramètre. Donc, la modification du paramètre n'influe pas la variable passée. Par contre, si le paramètre est une référence à la variable passée, alors la valeur de la variable passée change en même que temps celle du paramètre, puisque c'est une référence. Voici un exemple :
c. Obtenir une valeur d'une fonction
Si une fonction peut recevoir des données, elle peut aussi en produire, et permettre de l'utiliser dans une autre fonction. Pour les fonctions, les données produites sont appelées les retours de fonction, et l'obstention de ces données est appelé un retour. Cette valeur peut être utilisé via un simple appel à la fonction (elle est, bien évidement, utilisée là où la fonction est appelée), comme n'importe quelle variable classique. Bien que la fonction puisse retourner de plein de façons possibles, on va garder une démarche proche du C++ pour procéder. Comme le C++ utilise un typage statique, nous allons dire qu'un seul type peut être renvoyé par la fonction. Dans notre pseudo-code, nous allons remplacer le mot "fonction" par le nom de ce type, par exemple :
B. Les fonctions en C++
a. Définir une fonction
La base de tout cela est, bien évidemment, de définir une fonction. J'ai choisi une syntaxe pseudo-code assez proche de ce que propose le C++. En effet, en C++, une fonction se définit comme ça :
La valeur retournée sera la valeur situé après "return". Dés que la valeur est retournée, la fonction s'arrête. Cependant, comme en pseudo-code, une fonction peut avoir plusieurs "return" dans sa définition. Dans ce cas là, il est conseillé d'utiliser une condition, comme ça :
Pour attribuer des valeurs par défaut aux paramètres la syntaxe est la même qu'en pseudo-code. Voici un très simple exemple :
Si une fonction qui doit retourner un type ne retourne rien, des comportements inattendus peuvent apparaître, de l'ignorance de la valeur avec le compiler MSVC, au crash avec le compiler GCC. Heuresement, il existe un type pour les fonctions ne retournant rien : void. Dans ce cas, le "return" peut être utilisé pour sortir de la fonction, mais aucune valeur n'est obstensible via cette dernière. Cependant, vous pouvez aussi ne pas mettre de "return" du tout, le compiler s'en chargera. Voici un exemple inspiré des exemples précédents :
b. Utiliser proprement une fonction
Savoir écrire des fonctions, c'est bien. Savoir les utiliser, c'est encore mieux. En C++, il existe pas mal de petits conseils pour pouvoir rendre ses fonctions les plus performantes possibles.
L'avantage de séparer le code en fonction est de pouvoir facilement modifier ce code après, sans grandes difficultés. On peut donc optimiser le code autant que l'on veut, facilement. Une opération pouvant prendre beaucoup de temps lors de l'appel d'une fonction est la copie des paramètres nécessaires. En effet, si nous passons les paramètres comme vu en haut, ils seront juste copiés. D'ailleurs, pour mesurer l'optimisation d'un algorithme, on doit utiliser sa complexité. Cependant, vous pouvez aussi utiliser en paramètre une référence de variable, pour éviter la phase de copie. Dans ce cas là, la variable ne sera pas copiée, mais référencée, ce qui peut faire gagner beaucoup de temps pour des grosses variables. Voici un exemple pour la fonction vue juste au dessus :
Vous pouvez aussi utiliser des fonctions en elles même comme une boucle. Ce procédé s'appelle la récursion. Il s'agit d'une fonction qui s'appelle elle même. Il faut cependant faire extrêmement attention à ce que la fonction ne s'appelle pas à l'infini, sinon le programme freeze. Le meilleur moyen est d'utiliser une condition où la fonction n'est plus appelée, nommée une condition de sortie. Voici un exemple d'utilisation avec la fonction effectuer_multiplication :
Pour en finir avec tout ça, nous allons parler d'une fonction que nous avons déjà vu : la fonction "main". Pour rappel, il s'agit de la fonction appelée automatiquement par le système d'exploitation lors du lancement du programme. Elle est de type "int", donc nombre entier signé. En effet, le retour de la fonction "main" est le retour du programme entier. En général, 0 veut dire "exécution normale". Ce nombre retourné est le nombre affiché à la fin de la console du programme exécuté. De plus, la fonction "main" peut aussi prendre deux paramètres, passés, encore une fois, par le système d'exploitation. Ces deux paramètres sont un "int", nommé par convention "argc", et un tableau de "char*", nommé par convention "argv". Nous verrons comment utiliser les tableaux dans les prochains cours. Ces deux paramètres représentent les arguments passés au programme par le système d'exploitation. Ces arguments représentent les mêmes que les arguments passés lors de l'exécution du programme via la ligne de commande, c'est pour ça qu'ils sont en format "char*" (texte bas niveau). À noter que quel que soit la façon de lancer le programme, il y aura au moins un argument : le chemin d'accés du programme exécuté.
c. Les méthodes
Pour finir les fonctions, nous allons voir un type de fonction extrêmement important à connaître, bien que nous n'avons pas encore les outils pour les comprendre totalement : les méthodes.
Quand vous créez une variable, quel quelle soit, elle obéit à ce que son type lui permet de faire, comme nous l'avons vu au premier cours. Certains types permettent d'utiliser des fonctions directement sur la variable. Ces fonctions sont nommés des méthodes. C'est un moyen de se passer de l'utilisation de seulement des références pour modifier une variable via une fonction. En général, elles sont définies en même temps que le type, donc pas besoin de le faire nous même. Pour les utiliser, il faut utiliser un opérateur spécial avec la variable : l'opérateur d'accès aux membres de la variables, qui s'écrit ".". Nous définirons le mot "membre" dans un prochaine cours. Malheuresement, les types fondamentaux ne peuvent pas être utilisés via des membres. Heuresement, les autres, comme les "string", peuvent. Voici un exemple d'utilisation de l'opérateur, avec un "string" et une de ses méthodes "size()" :
En général, toutes les méthodes utilisables dans un type sont présentées dans un texte nommé "la documentation du type". Par exemple, toutes les méthodes utilisables dans "string" sont présentes sur ce site web. Les opérateurs peuvent aussi être considérés comme des méthodes. Comme nous n'avons pas vu les classes pour le moment, certains termes peuvent rester abstraits. Essayez de vous concentrer sur ce que vous savez déjà, pour pouvoir faire de grandes choses !