Outils d'utilisateurs

Outils du Site


fonctions

Différences

Cette page vous donne les différences entre la révision choisie et la version actuelle de la page.

Lien vers cette vue

fonctions [2016/05/04 14:18]
bouli1515 erreur de markdown + orthographe
fonctions [2016/06/28 16:27] (Version actuelle)
gbdivers
Ligne 1: Ligne 1:
  
-^ Chapitre précédent ^ [[programmez_avec_le_langage_c|Sommaire principal]] ^ Chapitre suivant ^+[[validation_motifs|Chapitre précédent]] ^ [[programmez_avec_le_langage_c|Sommaire principal]] ^ [[parametres_arguments|Chapitre suivant]] ^
  
 ====== Créer des fonctions ====== ====== Créer des fonctions ======
Ligne 27: Ligne 27:
 D D
 </code> </code>
 +
 +{{ :functions.png |}}
  
 En programmation procédurale, une suite d'instructions est placée dans une procédure (on parle de "fonction" en C++) pour pouvoir être appelé plusieurs fois. En programmation procédurale, une suite d'instructions est placée dans une procédure (on parle de "fonction" en C++) pour pouvoir être appelé plusieurs fois.
Ligne 36: Ligne 38:
  
 void f() { void f() {
-    std::cout << "f_A" << std::endl; +    std::cout << "  1" << std::endl; 
-    std::cout << "f_B" << std::endl;+    std::cout << "  2" << std::endl;
 } }
  
Ligne 53: Ligne 55:
 <code> <code>
 A A
-f_A +  1 
-f_B+  2
 B B
-f_A +  1 
-f_B+  2
 C C
 </code> </code>
 +
 +{{ :function2.png |}}
  
 Pour suivre le déroulement d'un tel programme : Pour suivre le déroulement d'un tel programme :
Ligne 72: Ligne 76:
 Ce type de programmation est encore relativement simple à suivre, il suffit de lire les instructions une par une. La difficulté vient si vous avez beaucoup de fonctions, il est pénible d'arrêter la lecture toutes les 2 lignes pour aller lire une autre partie de code. (Essayez de lire un livre et d'aller consulter un dictionnaire tous les 2 phrases, vous comprendrez). Ce type de programmation est encore relativement simple à suivre, il suffit de lire les instructions une par une. La difficulté vient si vous avez beaucoup de fonctions, il est pénible d'arrêter la lecture toutes les 2 lignes pour aller lire une autre partie de code. (Essayez de lire un livre et d'aller consulter un dictionnaire tous les 2 phrases, vous comprendrez).
  
-En pratique, cela ne sera pas nécessaire si vous écrivez correctement vos fonctions, en leur donnant un nom explicite. Avec le nom de la fonction, vous pourrez comprendre ce qu'elle fait et continuer la lecture du code, sans devoir aller lire les instructions dans la fonction appellée.+En pratique, cela ne sera pas nécessaire si vous écrivez correctement vos fonctions, en leur donnant un nom explicite. Avec le nom de la fonction, vous pourrez comprendre ce qu'elle fait et continuer la lecture du code, sans devoir aller lire les instructions dans la fonction appelée.
  
-Et en fait, c'est ce que vous faite depuis le début de ce cours. Vous avez déjà utiliser des fonctions, comme par exemple les algorithmes de la bibliothèque standard. Par exemple, le code suivant :+Et en fait, c'est ce que vous faite depuis le début de ce cours. Vous avez déjà utilisé des fonctions, comme par exemple les algorithmes de la bibliothèque standard. Dans le code suivant :
  
 <code cpp> <code cpp>
-vector<int> v { 1, 8, 3, 5, 2, 9 };+std::vector<int> v { 1, 8, 3, 5, 2, 9 };
 std::sort(begin(v), end(v)); std::sort(begin(v), end(v));
 std::cout << v.front() << std::endl; std::cout << v.front() << std::endl;
 </code> </code>
  
-Lorsque vous lisez la ligne avec ''std::sort'', vous comprenez que le tableau est trié et que vous afficher ensuite le premier élément. Vous n'avez pas besoin d'aller lire le code de cette fonction pour comprendre ce code et déterminé que ce code va afficher la valeur "1" (première valeur du tableau après le tri).+Lorsque vous lisez la ligne avec ''std::sort'', vous comprenez que le tableau est trié et que vous affichez ensuite le premier élément. Vous n'avez pas besoin d'aller lire le code de cette fonction pour comprendre que ce code va afficher la valeur "1" (première valeur du tableau après le tri).
  
-C'est une règle générale qui faut retenir quand vous créez vos fonctions : il faut pouvoir les comprendre et les utiliser sans devoir aller lire le code de la fonction.+C'est une règle générale qu'il faut retenir quand vous créez une fonction : il faut pouvoir (idéalement) la comprendre et l'utiliser sans devoir aller lire le code de la fonction ou la documentation. (Le nom de la fonction et des paramètres doit être la première documentation).
  
 Un autre exemple de fonction que vous connaissez bien maintenant : la fonction ''main'' : Un autre exemple de fonction que vous connaissez bien maintenant : la fonction ''main'' :
  
 <code cpp> <code cpp>
-int main() { +int main() {}
-}+
 </code> </code>
 +
 +
 +===== Pourquoi créer des fonctions ? =====
 +
 +<note>**À rédiger...**
 +
 +Savoir utiliser des fonctions ne consiste pas seulement à connaitre les syntaxes pour écrire et utiliser des fonctions. L'objectif est avant tout de savoir décomposer une problématique complexe en sous-problématiques de plus en plus simples, de façon à arriver à des solutions connues ou facilement implémentables.
 +
 +{{ :function.png |}}
 +
 +Une solution sera généralement implémentée sous forme d'une fonction, qui aura idéalement les qualités suivantes :
 +
 +  * courte : en quelques lignes (quelques lignes a quelques dizaines de lignes) ;
 +  * simple a implémenter : qui utilise au maximum d'autres fonctions, en particulier des classes et algorithmes de la bibliothèques standard ;
 +  * compréhensible : à partir du nom de la fonction et des paramètres (et de la documentation), n'importe qui peut comprendre ce que fait la fonction ;
 +  * générique : la fonction peut être utilisée avec de nombreux types de données ;
 +  * testable : il est facile d’écrire des tests permettant de vérifier le comportement d'une fonction.
 +
 +exemple de démarche partant d'une problématique en solutions simples
 +</note>
  
  
 ===== Syntaxe de base ===== ===== Syntaxe de base =====
  
-Une fonction était définie par :+Une fonction est définie par :
  
   * des informations en entrée et sortie   * des informations en entrée et sortie
Ligne 108: Ligne 131:
 </code> </code>
  
-Les paramètres en entrées et sortie seront détaillés ensuite. Si pas d'information à passer, il est possible de ne pas avoir de paramètre en entrée et d'utiliser ''void'' comme paramètre de sortie (qui signifie "pas d'information" dans ce cas).+<note>**Un peu de vocabulaire** 
 + 
 +La **signature** d'une fonction est le nom d'une fonction et la liste des types des paramètres. Une **déclaration** de fonction est une fonction sans implémentation (sans le bloc d'instruction, qui est remplacé par un point-virgule final). La définition d'une fonction est la fonction complète avec l’implémentation. 
 + 
 +<code> 
 +void f(int);      // déclaration 
 +void f(int) { ... }  // définition 
 +</code> 
 + 
 +Une déclaration ne sert qu'a "dire" (déclarer) au compilateur qu'un identifiant existe, alors qu'une définition explique à quoi correspond un identifiant (ce qu'il est, comment il est définit). 
 + 
 +Une même fonction peut avoir plusieurs déclarations, mais ne peut avoir qu'une seule définition (ODR, //One Definition Rule//). 
 + 
 +A noter la similarite entre la syntaxe pour declarer une fonction et l'initialisation d'une variable avec des parentheses : 
 + 
 +<code cpp> 
 +int x(int);  // un type = declaration d'une fonction 
 +int y(123);  // une valeur = definition d'une variable 
 +int z();     // rien = fonction ou variable ? 
 +</code> 
 + 
 +La derniere syntaxe correspond en fait a la declaration d'une fonction, qui ne prend aucun parametre et retourne un entier. Le probleme est que cette syntaxe est regulierement confondue avec une initialisation par defaut d'une variable. Ce qui produira bien sur une erreur de compilation lorsque vous esserez d'utiliser une fonction comme si c'etait une variable. 
 +</note> 
 + 
 +Les paramètres en entrées et sortie seront détaillés ensuite. S'il n'y a pas d'information à passer, il est possible de ne pas avoir de paramètre en entrée et d'utiliser ''void'' comme paramètre de sortie (qui signifie "pas d'information" dans ce cas).
  
 <code> <code>
Ligne 124: Ligne 171:
  
 Par exemple : Par exemple :
- 
  
 <code> <code>
Ligne 142: Ligne 188:
 </code> </code>
  
-Ces noms de fonction sont valides. Les 2 premières "f" et "g", etc ne sont pas explicite, donc impossible de savoir ce qu'elles font sans lire leur code. Donc mauvais nom en général (mais utilisé souvent comme des codes d'explication, pour nommer des fonctions qui n'ont pas d'autre utilité que d'expliquer quelque chose ou pour lequel le nom importe peu. Ne pas utiliser dans des codes réels).+Note : dans ces codes, ''{}'' indique un bloc d'instructions... sans instructions. Donc ces fonctions ne font rien, ce sont juste des exemples pour présenter la syntaxe. 
 + 
 + 
 +Tous ces noms de fonction sont valides. La première liste de noms ("f""g", etc. jusqu’à "bar") ne sont pas explicite, donc impossible de savoir ce qu'elles font sans lire leur code. Ce sont donc de mauvais noms en général (mais utilisé souvent comme des codes d'explication, pour nommer des fonctions qui n'ont pas d'autre utilité que d'expliquer quelque chose ou pour lequel le nom importe peu. Ne pas utiliser dans des codes réels).
  
-Les 2 fonctions suivantes "une_fonction_quelconque" et "UneAutreFonction" ont des noms plus explicite. Notez l'utilisation des _ et majuscule pour faciliter la lecture. Forme préférer dans vos codes, avec des noms explicites.+Les 2 fonctions suivantes "une_fonction_quelconque" et "UneAutreFonction" ont des noms plus explicite. Notez l'utilisation des ''_'' et majuscule pour faciliter la lecture. Forme à préférer dans vos codes, avec des noms explicites.
  
 La suivante est un exemple de fonction valide, mais peu lisible à cause de la mauvaise utilisation des majuscules. La suivante est un exemple de fonction valide, mais peu lisible à cause de la mauvaise utilisation des majuscules.
  
-Les 2 dernières utilise un nom avec un numéro. C'est également un exemple de nom peu explicite, à éviter dans un code réel.+Les 2 dernières utilisent un nom avec un numéro. C'est également un exemple de nom peu explicite, à éviter dans un code réel.
  
-Exemple de noms pas valides :+Exemple de noms invalides :
  
 <code> <code>
Ligne 157: Ligne 206:
 </code> </code>
  
-Pour appeler une fonction, simplement avec le nom.+===== Appel de fonction ===== 
 + 
 +Pour appeler une fonction qui ne prend pas d'arguments et qui ne retourne pas d'informationvous pouvez l'appeler directement en utilisant son nom et des parentheses vides. Vous avez utilise cette syntaxe de nombreuses fois, par exemple pour la fonction ''size'' de ''std::string'' ou la fonction ''clear'' de ''std::vector''.
  
 <code> <code>
Ligne 163: Ligne 214:
 </code> </code>
  
-===== Portée des variables =====+Par exemple :
  
-__ pas ici, mettre dans un chapitre avec les bloc d'instruciton __+<code cpp> 
 +#include <iostream>
  
-Variable sont locale à un bloc. Dès que l'on sort du bloc, la variable est détruite. Mais variable accessibles dans bloc inclus : 
- 
-<code> 
-int main() { 
-    int i {}; 
-    { 
-        std::cout << i << std::endl; // ok 
-    } 
-    std::cout << i << std::endl; // ok 
-} 
-</code> 
- 
-Au contraire, une variable définie dans un bloc n'est pas accessible en dehors : 
- 
- 
-<code> 
-int main() { 
-    { 
-        int i {}; 
-        std::cout << i << std::endl; // ok 
-    } 
-    std::cout << i << std::endl; // erreur, i n'existe plus 
-} 
-</code> 
- 
-__ variable globale, en dehors des fonctions : fu.. __ 
- 
-Idem entre bloc : 
- 
-<code> 
-int main() { 
-    { 
-        int i {}; 
-    } 
-    { 
-        std::cout << i << std::endl; // erreur, i n'existe pas 
-    } 
-} 
-</code> 
- 
-Dans ce code, même si les 2 blocs sont des sous-bloc du bloc de la fonction main, ce sont deux bloc différents 
- 
-Plus généralement : 
- 
-<code> 
-{ 
-    int i {}; 
-    ...  // i existe ici 
-}        // i est détruit ici 
-</code> 
- 
-===== Arguments de fonctions ===== 
- 
-Même situation avec fonction : 
- 
-<code> 
 void f() { void f() {
-    std::cout << << std::endl; // erreur, i n'existe pas dans ce bloc+    std::cout << "hello, world" << std::endl;
 } }
  
 int main() { int main() {
-    int i {}; +    f();
-    f(); // i existe lors que l'on appelle f()+
 } }
 </code> </code>
  
-Dans ce code, même si la variable i existe lorsque l'on appelle la fonction f, le bloc d'instructions de f n'est pas sous-bloc de main, donc pas accessible. +affiche :
- +
-Pour envoyer des informations dans la fonction, on a déjà vu avec les lambda : utilisation de paramètres de fonction. Déclarés entre les parenthèses :+
  
 <code> <code>
-void f(paramètres de fonction) { +hello, world
-+
- +
-int main() { +
-    f(arguments de fonction); +
-}+
 </code> </code>
  
-Paramètres = liste de 01 ou plusieurs (type + nom). Par exemple, pour envoyer 1 valeur entière : +Le compilateur lit le code dans l'ordre où il est écritdonccomme pour les variables, vous ne pouvez pas utiliser une fonction avant qu'elle ne soit définie. Le code suivant est donc invalide, même si la fonction existe :
- +
-<code> +
-void f(int i) { // création de i +
-}               // destruction de i +
-</code> +
- +
-Les paramètres ont même portée que variables localesdepuis le début du bloc jusqu'à la fin +
- +
-Pour appeler la fonction, donne une liste d'argumentqui peut être une littérale, une variable ou une fonction qui retourne un valeur de même type. Liste des arguments doit correspondre à la liste des paramètres : +
- +
-<code> +
-void f() { +
-    std::cout << "f()" << std::endl; +
-+
- +
-void g(int i) { +
-    std::cout << "g() avec i=" << i << std::endl; +
-}+
  
 +<code cpp>
 int main() { int main() {
-    f();    // ok +    f();
-    f(123); // erreur, trop d'argument +
-     +
-    g();    // erreur, pas assez d'argument +
-    g(123); // ok+
 } }
-</code> 
  
-Exemple de message d'erreur pour f (clang) +void f() {}
- +
-<code> +
-main.cpp:13:5: error: no matching function for call to 'f' +
-    f(123); +
-    ^ +
-main.cpp:3:6: note: candidate function not viable: requires 0 arguments, but 1 was provided +
-void f() { +
-     ^+
 </code> </code>
  
-Le compilateur indique qu'il ne trouve pas de fonction f correspondant à l'appel f(123) ("no matching function"). Il indique à la ligne suivante qu'il connait une fonction f ("candidate function"), mais qui prend 0 arguments ("requires 0 arguments") alors que l'appel donne 1 arguemnt ("but 1 was provided") +affiche le message d'erreur suivant :
- +
-Pour l'appel de g, le message :+
  
 <code> <code>
-main.cpp:15:5: error: no matching function for call to 'g+main.cpp:2:5: error: use of undeclared identifier 'f
-    g();   +    f();
     ^     ^
-main.cpp:7:6: note: candidate function not viable: requires single  
-argument 'i', but no arguments were provided 
-void g(int i) { 
-     ^ 
 </code> </code>
  
-De la même manière, le compilateur indique qu'il ne trouve pas de fonction correspondant à g(), mais qu'il trouve une fonction g qui prend 1 argument.+Ce qui signifie "utilisation d'un identifiant non déclaré" (sous entendant "non déclaré dans le code qui précède la ligne de code qui utilise cet identifiant).
  
-On peut utiliser n'importe quel type copiable :+Il est possible de déclarer une fonction et de la définir plus tard, en utilisant une déclaration anticipée (//forward declaration//), vous verrez cela dans la suite de ce cours.
  
-<code> +Vous pouvez appeler plusieurs fois une même fonction (comme vu dans le second code) ou appeler une fonction depuis une autre fonction(C'est d'ailleurs ce que vous faites quand vous appeler une fonction quelconque depuis la fonction ''main'').
-void f(int i, double d, string s, vector<int> v) { +
-+
-</code> +
- +
-Au besoin, le compilateur réalise une conversion pour adapter les argumentsPar exemple si on écrit une fonction qui prend un long int et qu'on passe un int :+
  
 <code cpp> <code cpp>
-void f(long int i) { +void g() {} 
-}+ 
 +void f() { g(); // f appelle g
  
 int main() { int main() {
-    f(123); // 123 est une littérale de type int +    f(); 
-            // peut être converti implicitement en long int+    f();  // plusieurs appels de la fonction f
 } }
 </code> </code>
  
-===== Surcharge de fonction ===== +Une fonction qui est définie dans une autre fonction est appelée une fonction imbriquée (//nested function//). En C++, à part le cas particulier des fonctions lambdas que vous verrez ensuiteune fonction ne peut pas être imbriquée dans une autre fonction.
- +
-(ou //overloading// ou polymorphisme ad-hoc) +
- +
-Polymorphisme : plusieurs fonctions de même nomDes fonctions peuvent avoir le même nomtant que les paramètres sont différents : +
- +
-<code> +
-void f(int i) { +
-    std::cout << "f(int) avec i=" << i << std::endl; +
-+
- +
-void f(string s) { +
-    std::cout << "f(string) avec s=" << s << std::endl; +
-+
-</code> +
- +
-Le compilateur choisit la fonction correspondante, selon le type que l'on donne en argument :+
  
 <code cpp> <code cpp>
-void f(int i) { +void f() {  
-    std::cout << "f(int) avec i=" << i << std::endl; +    void g(); // déclaration d'une fonction imbriquée : erreur
-+
- +
-void f(long int i) { +
-    std::cout << "f(long int) avec i=" << i << std::endl; +
-+
- +
-int main() { +
-    f(1); // 1 est une littérale de type int +
-    f(2L); // 2L est une littérale de type long int +
- +
-    int i { 1 }; +
-    f(i); +
- +
-    long int l { 2 }; +
-    f(l);+
 } }
 </code> </code>
  
-affiche :+Les fonctions doivent être déclarées dans un espace de noms - qui sera vu dans le chapitre sur la création de bibliothèques - ou dans une classe - qui sera vu dans la partie programmation orientée objet. Quand vous déclarer une fonction directement en dehors de la fonction ''main'', comme vous le faites depuis le début de ce chapitre, vous les déclarez en fait dans un espace de noms global et anonyme. Cela sera également vu bientôt.
  
-<code> +Un cas particulier de fonction qui appelle une fonction : il est possible d’écrire une fonction qui s'appelle elle mêmeLe code suivant est valide (tout au moins en termes de syntaxe) :
-f(int) avec i=1 +
-f(long int) avec i=2 +
-f(int) avec i=1 +
-f(long int) avec i=2 +
-</code> +
- +
-Le compilateur commence par rechercher s'il connait une fonction avec le nom correspondant. Par exemple pour f(1), il trouve 2 fonctions f(int) et f(long int). Ensuite il regarde si l'un des types en paramètre correspondant au type en argumentIci, c'est le cas, il appelle donc f(int)+
- +
-Si on écrit :+
  
 <code cpp> <code cpp>
-#include <iostream> +void f() { 
- +    f();  // f appelle f
-void f(long int i) { +
-    std::cout << "f(long int) avec i=" << i << std::endl; +
-+
- +
-int main() { +
-    f(1); // 1 est une littérale de type int+
 } }
 </code> </code>
  
-Le compilateur trouve la fonction f, mais le paramètre ne correspond pas. Il regarde s'il peut faire une conversion. Ici, oui, on peut convertir implicitement un int en long intil convertie donc 1 en 1L et appelle f(long int).+Un code qui s'appelle lui-même est appelle un code récursifC'est une approche intéressante pour résoudre de très nombreuses problématiques. Vous verrez dans les exercices et compléments quelques exemples d'algorithmes récursifs.
  
-S'il ne trouve pas de conversion possibleil lance un message d'erreurPar exemplesi on appelle f("du texte"), le compilateur donne :+Cependantle code précédent est trop simpliste et produira un crash a l’exécution du programme. La raison est très simple : la fonction f va appeler la fonction f qui va appeler la fonction f qui va appeler la fonction f... et ainsi de suite, a l'infiniOu plus précisémentpuisqu'un ordinateur n'a pas des capacités infiniesjusqu’à ce que les ressources allouées au programme soient toutes utilisées et que le programme crash.
  
-<code> 
-main.cpp:19:5: error: no matching function for call to 'f' 
-    f("une chaine"); 
-    ^ 
-main.cpp:3:6: note: candidate function not viable: no known conversion  
-from 'const char [11]' to 'int' for 1st argument 
-void f(int i) { 
-     ^ 
-main.cpp:7:6: note: candidate function not viable: no known conversion  
-from 'const char [11]' to 'long' for 1st argument 
-void f(long int i) { 
-     ^ 
-1 error generated. 
-</code> 
  
-Ce qui signifie qu'il ne trouve aucune fonction correspond à l'appel de f("une chaine"), mais qu'il a 2 candidat (2 fonction qui ont le même nom) mais sans conversion possible ("no known conversion").+===== Variable locale à une fonction =====
  
-Au contrairedans certain cas, il aura plusieurs possible possible, soit parce que vous déclarez par erreur 2 fonctions avec les mêmes paramètres, soit parce que le compilateur peut faire 2 conversions pour 2 typesDans le premier cas : +Un petit rappel sur les notions de portée et de durée de vie. Pour le moment, vous n'avez utilise que des variables locales à une fonctionUne telle fonction est déclarée dans un bloc dans une fonction (dans la fonction ''main'' jusque maintenantmais c'est valide pour n'importe quelle fonction que vous pouvez créer). Cette variable est utilisable a partir du moment ou elle est définie et jusqu’à la fin du bloc.
- +
-<code> +
-void f() { +
-   std::cout << "première fonction f" << std::endl; +
-+
- +
-void f() { +
-   std::cout << "seconde fonction f" << std::endl; +
-+
-</code> +
- +
-produit le message  : +
- +
-<code> +
-main.cpp:7:6: error: redefinition of 'f' +
-void f(int i) { +
-     ^ +
-main.cpp:3:6: note: previous definition is here +
-void f(int i) { +
-     ^ +
-</code> +
-      +
-Quand le compilateur arrive à la ligne 7 et rencontre la seconde fonction f (qu'il connait déjà)il prévient qu'il connait déjà ("redefinition of 'f'") et que la première version ("previous definition is here"se trouve à la ligne 3. +
- +
-L'autre cas est si plusieurs fonctions peuvent correspondent, l'appel est ambiguPar exemple :+
  
 <code cpp> <code cpp>
-#include <iostream> +
- +    int i {} // définition d'une variable 
-void f(int i) +    ... 
-    std::cout << "f(int) avec i=" <<; i << std::endl; +             // fin de portée de la variable i
-+
- +
-void f(long int i) { +
-    std::cout << "f(long int) avec i=" << i << std::endl; +
-} +
- +
-int main() { +
-    f(1u); // 1 est une littérale de type unsigned int +
-}+
 </code> </code>
  
-affiche le message d'erreur :+Les notions de portée (//scope//) et durée de vie (//lifetime//) sont très proches (pour le moment) :
  
-<code>+  * la portée est quand la variable est utilisable dans le code ; 
-main.cpp:12:5: error: call to 'f' is ambiguous +  * la durée de vie est quand la variable existe en mémoire de l'ordinateur.
-    f(1u); // 1 est une littérale de type int +
-    ^ +
-main.cpp:3:6: note: candidate function +
-void f(int i) { +
-     ^ +
-main.cpp:7:6: note: candidate function +
-void f(long int i) { +
-     ^ +
-</code>+
  
-Il existe une conversion de unsigned int vers int et vers long int. Il n'y a pas de priorité dans les conversionsle compilateur ne sait pas quelle conversion choisir et donc quelle fonction appeler. L'appel est ambuigu ("call to 'f' is ambiguous"), il trouve deux fonctions candidate ("candidate function").+Pour des variables locales, la portée et la durée de vie d'une variable sont les mêmesmais vous verrez qu'il existe une autre catégorie de variables (les variables dynamiques), dont la portée et la durée de vie peuvent être différentes.
  
-La méthode qui permet au compilateur de trouver la fonction correspondant à une appel s'appelle la résolution des noms (name lookup)+<note>**Variables globales**
  
-<note warning>Note sur bool+Il existe en fait une troisième catégorie : les variables globales, qui ont généralement une portée globale (accessible n'importe ou dans le programme - c'est par exemple le cas de ''std::cout'') et une durée de vie permanente (création au lancement du programme et destruction lorsque le programme se termine).
  
-Comme cela a déjà été expliqué, certains types, dont les littérales chaînes (et plus généralement les pointeurs), sont convertissable automatiquement en booléen. Si on écrit la surcharge suivante : +L'utilisation des variables globales est problématique en termes de conception et d'utilisationelles doivent être évitée au maximumC'est a dire toujourssauf quand vous avez de très bonnes justifications pour ne pas respecter cette régle.
- +
-<code cpp> +
-void foo(bool) { std::cout << "f(bool)" << std::endl; } +
-void foo(string const&) { std::cout << "f(string)" << std::endl; } +
- +
-foo("abc"); +
-</code> +
- +
-Ce code ne va pas afficher ''f(string)'', mais ''f(bool)''Si on ajoute une fonction ''f(const char*)'' et sera appelée en premier. La raison est que la littérale chaîne est de type ''const char*''les fonctions seront appelée dans l'ordre suivant : +
- +
-  * f(const char*) : par de conversion entre l'argument et le paramètre ; +
-  * f(bool) : conversion automatique ; +
-  * f(string) : conversion passant par une classe. +
- +
-Donc attention lorsque vous écrivez une fonction qui prend bool, elle peut prendre aussi n'importe quel pointeur. +
- +
-Solution C++14 : écrire "abc"s pour créer une littérale de type string. +
- +
-__Détailler le name lookup__+
 </note> </note>
  
-===== Valeur par défaut ===== +Pour terminer ce rappel, les variables sont accessible dans le même bloc où elles sont définies et dans les blocs enfantsmais pas dans les blocs parents ou les blocs qui n'ont pas de relation hiérarchique.
- +
-On peut souhaiter pouvoir appeler une fonction avec et sans un argument. Par exemple f qui prend un entier ou 0 si on en donne aucune valeur +
- +
-Première solutionsurcharger la fonction :+
  
 <code> <code>
-void f() { 
-    std::cout << "f()" << std::endl; 
-} 
- 
-void f(int i) { 
-    std::cout << "f(int) avec i=" << i << std::endl; 
-} 
- 
 int main() { int main() {
-    f()   // ok, appel de f() +    
-    f(123); // ok, appel de f(int i)+        int i {}; 
 +        { 
 +            std::cout << i << std::endl; // ok, bloc enfant 
 +        } 
 +        std::cout << i << std::endl    // ok, même bloc 
 +    } 
 +    std::cout << << std::endl;         // erreur, bloc parent 
 +    { 
 +        std::cout << i << std::endl;     // erreur, bloc indépendant 
 +    }
 } }
 </code> </code>
  
-Le compilateur trouve à chaque fois deux fonctions avec le même nommais pas d’ambiguïté pour savoir laquelle appeler.+Cette notion est très importante à comprendre lorsque vous appelez des fonctions : les variables locales dans ces fonctions sont détruites à la fin de la fonction. Si vous appelez plusieurs fois une fonctionles variables locales sont créées puis détruites à chaque appel. Il n'est pas possible de transmettre des informations entre les différents appels via des variables locales.
  
-Possibilité de simplifier en donnant une valeur par défaut à un paramètre :+Considérez par exemple le code suivant.
  
 <code> <code>
-void f(int i = 0) { +#include <iostream>
-    std::cout << "f(int) avec i=" << i << std::endl; +
-+
-</code>+
  
-Dans ce cas, on indique que f peut prendre un entier. Si on ne donne pas de valeur, le compilateur peut utiliser la valeur par défaut : 
- 
-<code> 
-int main() { 
-    f();    // ok, appel de f(int i) avec i = 0 
-    f(123); // ok, appel de f(int i) avec i = 123 
-} 
-</code> 
- 
-Bien sûr, il ne faut pas laisser les 2 fonctions, pour éviter les ambiguité : 
- 
-<code> 
 void f() { void f() {
-    std::cout << "f()" << std::endl; +   int i;   // volontairement non initialisée 
-+   std::cout << << std::endl; 
- +   i = 123; 
-void f(int i = 0) { +   std::cout << i << std::endl;
-    std::cout << "f(int) avec i=" << i << std::endl;+
 } }
  
 int main() { int main() {
-    f();    // erreur, appel de f() ou de f(int i) avec i = 0 ?+    f(); 
 +    f();
 } }
 </code> </code>
  
-===== Retour de fonction ===== +Si vous dérouler les appels de fonction, vous pourriez penser que le code sera le suivant (en copiant-collant le code de la fonction ''f'' a la place des appels de fonction) :
- +
-Idem dans l'autre sens :+
  
 <code> <code>
-void f() { +#include <iostream>;
-    int i {}; +
-+
 int main() { int main() {
-    f(); // i existe dans f() +   int i  // volontairement non initialisée 
-    std::cout << i << std::endl; // erreur, n'existe pas dans ce bloc+   std::cout << << std::endl; 
 +   i = 123; 
 +   std::cout << i << std::endl; 
 +    
 +   // int i
 +   std::cout << i << std::endl; 
 +   i = 123; 
 +   std::cout << i << std::endl;
 } }
 </code> </code>
  
-Une variable déclarée localement dans une fonction ne sera pas accessible dans le code qui appelle cette fonction. Utilisation de retour de fonction, permet de retourner 1 seule valeur. Utilisation du mot-clé return pour indiquer la valeur que la fonction doit retourner et remplacer void par le type de la valeur que l'on veut retourner.+Et donc vous pouvez vous attendre a ce que le programme affiche :
  
 <code> <code>
-int f() { +xxx // une valeur aléatoire quelconque, puisque i n'est pas initialise 
-    int const i { 123 }; +123 
-    return i; +123 
-+123
- +
-int main() { +
-    int const j = f(); +
-    std::cout << j << std::endl; +
-}+
 </code> </code>
  
-Lorsque l'on appelle f, la variable i dans f est créée et initialisée avec la littérale 123après le return, la valeur de i est retournée au code appelantla variable j est créée et initialisée en copiant la valeur retournée par la fonction f (elle copie i) tandis que la variable i est détruite. +Mais il faut bien comprendre que ce qui se passe en réalité est que chaque appel de fonction va créer une nouvelle variable locale puis la détruireDonc même si le nom est le mêmec'est comme si la variable était différenteUn code plus correct serait celui ci :
- +
-retourner directement une valeur :+
  
 <code> <code>
-int f() { +#include <iostream>;
-    return 123; +
-+
 int main() { int main() {
-    int const j f()+   int i_1;   // volontairement non initialisée 
-    std::cout << << std::endl;+   std::cout << i_1 << std::endl; 
 +   i_1 123
 +   std::cout << i_1 << std::endl; 
 +    
 +   int i_2;   // volontairement non initialisée 
 +   std::cout << i_2 << std::endl; 
 +   i_2 = 123; 
 +   std::cout << i_2 << std::endl;
 } }
 </code> </code>
  
-Portée de variable fait que l'on peut utiliser 2 variables de même noms si portée différentesPar exemple :+Avec ce code, vous comprenez bien qu'il n'y a pas de raison que les deux appels a la fonction f possèdent la même valeur dans ''i''. Faites bien attention a cela.
  
-<code> +<note>**Garantie des comportements**
-int f() { +
-    int const i { 123 }; +
-    return i; +
-}+
  
-int main() { +En fait, si vous exécutez le code proposévous obtiendrez probablement :
-    int const i = f(); +
-    std::cout << i << std::endl; +
-+
-</code> +
- +
-Il faut bien comprendre ici que même si les 2 variables dans la fonction f et dans main s'appellent toutes les 2 "i"ce sont 2 variables différentes. +
- +
-Mot clé return retourne immédiatement de la fonction. Si on écrit :+
  
 <code> <code>
-int f() { +0 
-    int const i { 123 }; +123 
-    return i; +123 
-    std::cout << "on est après le return" << std::endl; // n'est jamais exécuté +123
-+
- +
-int main() { +
-    int const i = f(); +
-    std::cout << i << std::endl; +
-}+
 </code> </code>
  
-le cout après le return n'est pas exécuté.+La raison est que le compilateur peut optimiser au mieux le programme génère a partir de votre code et peut donc utiliser le même emplacement en mémoire entre les deux appels de fonction, ce qui donnera l'impression que la valeur de ''i'' est transmise.
  
-===== Exos =====+Mais bien sur, sur un code plus complexe, le comportement pourra être différent. La norme C++ ne garantie pas que le comportement du programme sera de toujours afficher une valeur aléatoire si la variable n'est pas initialisée ou d'afficher une valeur différente entre deux appels de fonction.
  
-http://cpp.developpez.com/tutoriels/Explicit-C++/ecrire-algorithme-standard/+Il est parfois possible d’écrire des codes de tests, pour comprendre le comportement d'un codeMais cela ne permet pas de savoir si c'est un comportement spécifique a votre compilateur ou si c'est un comportement garantie par la norme C++. 
 + 
 +Il est préférable d’écrire du code dont le comportement est garantie par la norme (et donc éviter par exemple les "undefined behavior", même s'ils peuvent parfois avoir le comportement attendu), pour avoir un code le plus portable possible. (Même si vous n’êtes pas a l’abri qu'un compilateur particulier ne respecte pas la norme C++ sur certains points, cela peut arriver. Mais c'est suffisamment rare, surtout pour les syntaxes de bases vues dans ce cours). 
 +</note>
  
-^ Chapitre précédent ^ [[programmez_avec_le_langage_c|Sommaire principal]] ^ Chapitre suivant ^+[[validation_motifs|Chapitre précédent]] ^ [[programmez_avec_le_langage_c|Sommaire principal]] ^ [[parametres_arguments|Chapitre suivant]] ^
  
-{{tag> Cours C++}} 
fonctions.1462364296.txt.gz · Dernière modification: 2016/05/04 14:18 par bouli1515