Les fonctions dans C++

de | 26 octobre 2018


Les fonctions permettent de structurer des programmes en segments de code pour effectuer des tâches individuelles.

En C ++, une fonction est un groupe d’instructions ayant un nom et pouvant être appelées à partir d’un point du programme. La syntaxe la plus courante pour définir une fonction est la suivante:

type nom (paramètre1, paramètre2, …) {instructions}

Où:
– type est le type de la valeur renvoyée par la fonction.
– name est l’identifiant par lequel la fonction peut être appelée.
– paramètres (autant que nécessaire): chaque paramètre est constitué d’un type suivi d’un identifiant, chaque paramètre étant séparé du suivant par une virgule. Chaque paramètre ressemble beaucoup à une déclaration de variable régulière (par exemple: int x) et agit en fait dans la fonction comme une variable régulière locale à la fonction. Le but des paramètres est de permettre de transmettre des arguments à la fonction à partir de l’emplacement d’où elle est appelée.
– déclarations est le corps de la fonction. C’est un bloc d’instructions entouré d’accolades {} qui spécifient ce que fait réellement la fonction.

Regardons un exemple:

// exemple de fonction
#include <iostream>
using namespace std;

int addition (int a, int b)
{
  int r;
  r = a + b;
  return r;
}

int main ()
{
  int z;
  z = addition (5,3);
  cout << "Le résultat est" << z;
}

Ce programme est divisé en deux fonctions: addition et principale. N’oubliez pas que, quel que soit l’ordre dans lequel ils sont définis, un programme C ++ commence toujours par appeler main. En fait, main est la seule fonction appelée automatiquement et le code de toute autre fonction n’est exécuté que si sa fonction est appelée depuis main (directement ou indirectement).

Dans l’exemple ci-dessus, main commence par déclarer la variable z de type int, puis exécute le premier appel de fonction: il appelle l’addition. L’appel à une fonction suit une structure très similaire à sa déclaration. Dans l’exemple ci-dessus, l’appel à l’addition peut être comparé à sa définition quelques lignes plus tôt:

Les paramètres de la déclaration de fonction ont une correspondance claire avec les arguments passés dans l’appel de fonction. L’appel transmet deux valeurs, 5 et 3, à la fonction; ceux-ci correspondent aux paramètres a et b, déclarés pour l’ajout de fonction.

Au moment où la fonction est appelée depuis le composant principal, le contrôle passe à la fonction addition: ici, l’exécution du paramètre principal est arrêtée et ne reprend que lorsque la fonction d’ajout est terminée. Au moment de l’appel de la fonction, la valeur des deux arguments (5 et 3) est copiée dans les variables locales int a et int b de la fonction.

Ensuite, à l’intérieur de l’addition, une autre variable locale est déclarée (int r), et au moyen de l’expression r = a + b, le résultat d’un plus b est attribué à r; ce qui, dans ce cas, où a est 5 et b est 3, signifie que 8 est attribué à r.

La déclaration finale dans la fonction:

return r;

Termine l’ajout de fonction et ramène le contrôle au point où la fonction a été appelée. dans ce cas: pour fonctionner principal. A ce moment précis, le programme reprend son cours sur le circuit principal et revient exactement au même moment où il a été interrompu par l’appel à l’addition. Mais en plus, puisque addition a un type de retour, l’appel est évalué comme ayant une valeur et cette valeur est la valeur spécifiée dans l’instruction de retour qui a terminé l’addition: dans ce cas particulier, la valeur de la variable locale r, qui à la moment de la déclaration a une valeur de 8.

Par conséquent, l’appel à l’addition est une expression avec la valeur renvoyée par la fonction, et dans ce cas, la valeur 8 est affectée à z. C’est comme si tout l’appel de fonction (addition (5,3)) était remplacé par la valeur renvoyée (c’est-à-dire 8).

Ensuite, main affiche simplement cette valeur en appelant:

 
cout << "Le résultat est" << z;

Une fonction peut en réalité être appelée plusieurs fois dans un programme, et son argument n’est naturellement pas limité aux littéraux:

// exemple de fonction
#include <iostream>
using namespace std;

soustraction int (int a, int b)
{
  int r;
  r = a-b;
  return r;
}

int main ()
{
  int x = 5, y = 3, z;
  z = soustraction (7,2);
  cout << "Le premier résultat est" << z << '\ n';
  cout << "Le deuxième résultat est" << soustraction (7,2) << '\ n';
  cout << "Le troisième résultat est" << soustraction (x, y) << '\ n';
  z = 4 + soustraction (x, y);
  cout << "Le quatrième résultat est" << z << '\ n';
}

Résultat de l’exécution ;

Le premier résultat est 5
Le deuxième résultat est 5
Le troisième résultat est 2
Le quatrième résultat est 6

Semblable à la fonction d’addition de l’exemple précédent, cet exemple définit une fonction de soustraction, qui renvoie simplement la différence entre ses deux paramètres. Cette fois, le principal appelle cette fonction plusieurs fois, ce qui montre davantage de façons possibles d’appeler une fonction.

Examinons chacun de ces appels, en gardant à l’esprit que chaque appel de fonction est lui-même une expression évaluée en tant que valeur renvoyée. Encore une fois, vous pouvez penser à cela comme si l’appel de fonction était lui-même remplacé par la valeur renvoyée:

z = soustraction (7,2);
cout << "Le premier résultat est" << z;

Si nous remplaçons l’appel de fonction par la valeur renvoyée (c’est-à-dire 5), nous aurions:

z = 5;
cout << "Le premier résultat est" << z;
Avec la même procédure, nous pourrions interpréter:
 
cout << "Le deuxième résultat est" << soustraction (7,2);
comme:
 
cout << "Le deuxième résultat est" << 5;

puisque 5 est la valeur renvoyée par soustraction (7,2).

Dans le cas de:

 
cout << "Le troisième résultat est" << soustraction (x, y);

Les arguments passés à la soustraction sont des variables au lieu de littéraux. Cela est également valable et fonctionne bien. La fonction est appelée avec les valeurs x et y au moment de l’appel: 5 et 3 respectivement, retournant 2 comme résultat.

Le quatrième appel est à nouveau similaire:

z = 4 + soustraction (x, y);

Le seul ajout étant que l’appel de fonction est également un opérande d’une opération d’addition. Là encore, le résultat est le même que si l’appel de fonction était remplacé par son résultat: 6. Notez que, grâce à la propriété commutative des ajouts, ce qui précède peut également s’écrire comme suit:

z = soustraction (x, y) + 4;

Avec exactement le même résultat. Notez également que le point-virgule ne va pas nécessairement après l’appel de la fonction, mais, comme toujours, à la fin de l’instruction entière. Encore une fois, la logique derrière peut être facilement vue en remplaçant les appels de fonction par leur valeur renvoyée:

z = 4 + 2; // identique à z = 4 + soustraction (x, y);
z = 2 + 4; // identique à z = soustraction (x, y) + 4;

Fonctions sans type. L’utilisation de void

La syntaxe indiquée ci-dessus pour les fonctions:

nom de type (argument1, argument2 …) {statement}

Requiert que la déclaration commence par un type. C’est le type de la valeur retournée par la fonction. Mais que se passe-t-il si la fonction n’a pas besoin de renvoyer une valeur? Dans ce cas, le type à utiliser est void, qui est un type spécial pour représenter l’absence de valeur. Par exemple, une fonction qui imprime simplement un message peut ne pas avoir besoin de renvoyer de valeur:

// exemple de fonction void
#include <iostream>
using namespace std;

annuler printmessage ()
{
  cout << "Je suis une fonction!";
}

int main ()
{
  printmessage ();
}

Résultat de l’execution:

Je suis une fonction!

void peut également être utilisé dans la liste de paramètres de la fonction pour spécifier explicitement que la fonction ne prend aucun paramètre réel lorsqu’elle est appelée. Par exemple, printmessage aurait pu être déclaré comme:

void printmessage (void)
{
  cout << "Je suis une fonction!";
}

En C ++, une liste de paramètres vide peut être utilisée à la place de void avec la même signification, mais l’utilisation de void dans la liste d’arguments a été popularisée par le langage C, dans les cas où il s’agit d’une exigence.

Les parenthèses qui suivent le nom de la fonction ne sont en aucun cas facultatives, ni dans sa déclaration, ni lors de son appel. Et même lorsque la fonction ne prend aucun paramètre, au moins une paire de parenthèses vides doit toujours être ajoutée au nom de la fonction. Voyez comment printmessage a été appelé dans un exemple précédent:

printmessage ();

Les parenthèses sont ce qui différencie les fonctions des autres types de déclarations. Ce qui suit n’appellerait pas la fonction:

message d'impression;

La valeur de retour de main

Vous avez peut-être remarqué que le type de retour de main est int, mais la plupart des exemples de ce chapitre et des chapitres précédents ne renvoient aucune valeur de main.

À lire aussi  La mémoire dynamique dans C++

Eh bien, il y a un problème: si l’exécution de main se termine normalement sans rencontrer d’instructions return, le compilateur suppose que la fonction se termine par une instruction return implicite:

return 0;

Notez que cela ne s’applique qu’à la fonction principale pour des raisons historiques. Toutes les autres fonctions avec un type de retour doivent se terminer par une instruction de retour appropriée incluant une valeur de retour, même si celle-ci n’est jamais utilisée.

Lorsque main renvoie zéro (implicitement ou explicitement), l’environnement est interprété comme indiquant que le programme s’est terminé avec succès. D’autres valeurs peuvent être renvoyées par main et certains environnements permettent à l’appelant d’accéder à cette valeur d’une manière ou d’une autre, bien que ce comportement ne soit pas requis ni nécessairement portable entre les plates-formes. Les valeurs de main qui doivent être interprétées de la même manière sur toutes les plateformes sont les suivantes:

Valeur description
Le programme a réussi
EXIT_SUCCESS Le programme a réussi
Cette valeur est défini dans <cstdlib>.
EXIT_FAILURE Le programme a échoué.
Cette valeur est défini dans <cstdlib>.

Parce que le retour implicite 0; L’instruction for main est une exception délicate. Certains auteurs considèrent qu’il est préférable d’écrire explicitement l’instruction.

Arguments passés par valeur et par référence

Dans les fonctions vues précédemment, les arguments ont toujours été passés par valeur. Cela signifie que, lors de l’appel d’une fonction, ce qui est transmis à la fonction sont les valeurs de ces arguments au moment de l’appel, qui sont copiées dans les variables représentées par les paramètres de la fonction. Par exemple, prenez:

int x = 5, y = 3, z;
z = addition (x, y);

Dans ce cas, l’addition de fonction passe 5 et 3, qui sont des copies des valeurs de x et y, respectivement. Ces valeurs (5 et 3) sont utilisées pour initialiser les variables définies en tant que paramètres dans la définition de la fonction, mais toute modification de ces variables dans la fonction n’a aucun effet sur les valeurs des variables x et y en dehors de celle-ci, car x et y étaient eux-mêmes pas transmis à la fonction sur l’appel, mais seulement des copies de leurs valeurs à ce moment.

Dans certains cas, cependant, il peut être utile d’accéder à une variable externe à partir d’une fonction. Pour ce faire, les arguments peuvent être passés par référence plutôt que par valeur. Par exemple, la fonction dupliquer dans ce code duplique la valeur de ses trois arguments, ce qui entraîne la modification des variables utilisées comme arguments par l’appel:

// passage de paramètres par référence
#include <iostream>
using namespace std;

void dupliquer (int & a, int & b, int & c)
{
  a * = 2;
  b * = 2;
  c * = 2;
}

int main ()
{
  int x = 1, y = 3, z = 7;
  dupliquer (x, y, z);
  cout << "x =" << x << ", y =" << y << ", z =" << z;
  return 0;
}

Résultat de l’exécution :

x = 2, y = 6, z = 14

Pour accéder à ses arguments, la fonction déclare ses paramètres comme références. En C ++, les références sont indiquées avec une esperluette (&) suivant le type de paramètre, comme dans les paramètres pris en double par l’exemple ci-dessus.

Lorsqu’une variable est passée par référence, ce qui est passé n’est plus une copie, mais la variable elle-même, la variable identifiée par le paramètre function, devient en quelque sorte associée à l’argument transmis à la fonction et à toute modification des variables locales correspondantes dans la fonction est reflétée dans les variables transmises en tant qu’arguments dans l’appel.

En fait, a, b et c deviennent des alias des arguments passés dans l’appel de fonction (x, y et z) et toute modification apportée à a dans la fonction modifie en fait la variable x en dehors de la fonction. Toute modification sur b modifie y, et toute modification sur c modifie z. C’est pourquoi, lorsque, dans l’exemple, la fonction dupliquée modifie les valeurs des variables a, b et c, les valeurs de x, y et z sont affectées.

Si au lieu de définir dupliquer comme:

void dupliquer (int & a, int & b, int & c)

Devait-il être défini sans les esperluettes comme:

void dupliquer (int a, int b, int c)

Les variables ne seraient pas transmises par référence, mais par valeur, créant à la place des copies de leurs valeurs. Dans ce cas, le résultat du programme aurait été les valeurs de x, y et z sans être modifié (c’est-à-dire 1, 3 et 7).

Considérations d’efficacité et références const

L’appel d’une fonction avec des paramètres pris par valeur entraîne la copie des valeurs. Il s’agit d’une opération relativement peu coûteuse pour les types fondamentaux tels que int, mais si le paramètre est d’un type composé large, il peut en résulter une certaine surcharge. Par exemple, considérons la fonction suivante:

string concatener (string a, string b)
{
  return a + b;
}

Cette fonction prend deux chaînes en tant que paramètres (par valeur) et renvoie le résultat de leur concaténation. En passant les arguments par valeur, la fonction oblige a et b à copier les arguments transmis à la fonction lors de son appel. Et si ces chaînes sont longues, cela peut impliquer de copier de grandes quantités de données uniquement pour l’appel de fonction.

Mais cette copie peut être totalement évitée si les deux paramètres sont des références:

string concatener (string & a, string & b)
{
  return a + b;
}

Les arguments par référence ne nécessitent pas de copie. La fonction agit directement sur les alias des chaînes passées en tant qu’arguments, et peut tout au plus signifier le transfert de certains pointeurs vers la fonction. À cet égard, la version de concaténation prenant des références est plus efficace que la version prenant des valeurs, car elle n’a pas besoin de copier des chaînes coûteuses à copier.

D’un autre côté, les fonctions avec des paramètres de référence sont généralement perçues comme des fonctions qui modifient les arguments transmis, car c’est pourquoi les paramètres de référence sont en réalité destinés à.

La solution consiste pour la fonction à garantir que ses paramètres de référence ne seront pas modifiés par cette fonction. Cela peut être fait en qualifiant les paramètres de constants:

string concatener (const string & a, const string & b)
{
  return a + b;
}

En les qualifiant de const, il est interdit à la fonction de modifier les valeurs de a ni b, mais elle peut accéder à leurs valeurs en tant que références (alias des arguments), sans avoir à effectuer de copies des chaînes.

Par conséquent, les références const fournissent une fonctionnalité similaire à la transmission d’arguments par valeur, mais avec une efficacité accrue pour les paramètres de types importants. C’est pourquoi ils sont extrêmement populaires en C ++ pour les arguments de types composés. Notez cependant que pour la plupart des types fondamentaux, il n’y a pas de différence notable d’efficacité, et dans certains cas, les références const peuvent même être moins efficaces!

La fonctions Inline

L’appel d’une fonction entraîne généralement un surcoût (empilement d’arguments, sauts, etc.) et, par conséquent, pour des fonctions très courtes, il peut s’avérer plus efficace d’insérer simplement le code de la fonction où elle est appelée au lieu d’effectuer le processus. d’appeler officiellement une fonction.

Le fait de faire précéder une déclaration de fonction avec le spécificateur inline indique au compilateur que le développement en ligne est préférable au mécanisme d’appel de fonction habituel pour une fonction spécifique. Cela ne change pas du tout le comportement d’une fonction, mais sert simplement à suggérer au compilateur que le code généré par le corps de la fonction doit être inséré à chaque endroit où la fonction est appelée, au lieu d’être appelé avec un appel de fonction régulier.

Par exemple, la fonction de concaténation ci-dessus peut être déclarée comme suit:

inline string concatener (string const & a, string const & b)
{
  return a + b;
}

Ceci informe le compilateur que, lorsque la concaténation est appelée, le programme préfère que la fonction soit développée en ligne, au lieu d’effectuer un appel normal. inline est uniquement spécifié dans la déclaration de fonction, pas quand elle est appelée.

À lire aussi  Les classes dans C++

Notez que la plupart des compilateurs optimisent déjà le code pour générer des fonctions inline lorsqu’ils voient une opportunité d’améliorer l’efficacité, même s’ils ne sont pas explicitement marqués avec le spécificateur inline. Par conséquent, ce spécificateur indique simplement au compilateur qu’inline est préféré pour cette fonction, bien que le compilateur soit libre de ne pas l’inligner et d’optimiser autrement. En C ++, l’optimisation est une tâche déléguée au compilateur, qui est libre de générer n’importe quel code tant que le comportement résultant est celui spécifié par le code.

Valeurs par défaut dans les paramètres

En C ++, les fonctions peuvent également avoir des paramètres facultatifs, pour lesquels aucun argument n’est requis dans l’appel, de telle sorte que, par exemple, une fonction avec trois paramètres peut être appelée avec deux seulement. Pour cela, la fonction doit inclure une valeur par défaut pour son dernier paramètre, qui est utilisé par la fonction lorsqu’elle est appelée avec moins d’arguments. Par exemple:

// valeurs par défaut dans les fonctions
#include <iostream>
using namespace std;

int divide (int a, int b = 2)
{
  int r;
  r = a / b;
  return (r);
}

int main ()
{
  cout << divide (12) << '\ n';
  cout << divide (20,4) << '\ n';
  return 0;
}

Résultat de l’exécution :

6
5

Dans cet exemple, il existe deux appels à la fonction diviser. Dans le premier:

divide (12)

L’appel ne transmet qu’un seul argument à la fonction, même si la fonction a deux paramètres. Dans ce cas, la fonction suppose que le deuxième paramètre est 2 (remarquez la définition de la fonction, qui déclare son deuxième paramètre comme int b = 2). Par conséquent, le résultat est 6.

Dans le deuxième appel:

divide (20,4)

L’appel passe deux arguments à la fonction. Par conséquent, la valeur par défaut pour b (int b = 2) est ignorée et b prend la valeur passée en argument, c’est-à-dire 4, ce qui donne un résultat de 5.

Déclarer des fonctions

En C ++, les identificateurs ne peuvent être utilisés dans les expressions qu’après avoir été déclarés. Par exemple, certaines variables x ne peuvent pas être utilisées avant d’être déclarées avec une instruction, telles que:

int x;

La même chose s’applique aux fonctions. Les fonctions ne peuvent pas être appelées avant d’être déclarées. C’est pourquoi, dans tous les exemples de fonctions précédents, les fonctions ont toujours été définies avant la fonction principale, qui est la fonction à partir de laquelle les autres fonctions ont été appelées. Si main était défini avant les autres fonctions, cela enfreindrait la règle selon laquelle les fonctions doivent être déclarées avant d’être utilisées et ne sont donc pas compilées.

Le prototype d’une fonction peut être déclaré sans réellement définir complètement la fonction, en donnant juste assez de détails pour permettre aux types impliqués dans un appel de fonction d’être connus. Naturellement, la fonction doit être définie ailleurs, comme plus tard dans le code. Mais au moins, une fois déclaré ainsi, il peut déjà être appelé.

La déclaration doit inclure tous les types impliqués (type de retour et type de ses arguments), en utilisant la même syntaxe que celle utilisée dans la définition de la fonction, mais en remplaçant le corps de la fonction (le bloc d’instructions) par un point-virgule de fin.

La liste de paramètres n’a pas besoin d’inclure les noms de paramètres, mais uniquement leurs types. Les noms de paramètres peuvent néanmoins être spécifiés, mais ils sont facultatifs et ne doivent pas nécessairement correspondre à ceux de la définition de la fonction. Par exemple, une fonction appelée protofunction avec deux paramètres int peut être déclarée avec l’une de ces instructions:

int protofunction (int en premier, int en second);
int protofunction (int, int);

Quoi qu’il en soit, l’inclusion d’un nom pour chaque paramètre améliore toujours la lisibilité de la déclaration.

// déclaration de prototypes de fonctions
#include <iostream>
using namespace std;

void impair (int x);
void meme (int x);

int main()
{
  int i;
  do {
    cout << "S'il vous plaît, entrez le numéro (0 pour sortir):";
    cin >> i;
    impair (i);
  } while (i! = 0);
  return 0;
}

void impair (int x)
{
  if ((x% 2)! = 0) cout << "C'est étrange. \ n";
  else meme (x);
}

vide meme (int x)
{
  if ((x% 2) == 0) cout << "Il est pair. \ n";
  else impair (x);
}

Résultat de l’exécution

S'il vous plaît, entrez le numéro (0 pour sortir): 9
C'est étrange.
S'il vous plaît, entrez le numéro (0 pour sortir): 6
C'est même.
S'il vous plaît, entrez le numéro (0 pour sortir): 1030
C'est même.
S'il vous plaît, entrez le numéro (0 pour sortir): 0
C'est même.

Cet exemple n’est en effet pas un exemple d’efficacité. Vous pouvez probablement écrire vous-même une version de ce programme avec la moitié des lignes de code. Quoi qu’il en soit, cet exemple illustre comment les fonctions peuvent être déclarées avant sa définition:

Les lignes suivantes:

void impair (int a);
void meme (int a);

Déclarez le prototype des fonctions. Ils contiennent déjà tout le nécessaire pour les appeler, leur nom, les types de leur argument et leur type de retour (vide dans ce cas). Avec ces déclarations de prototypes en place, elles peuvent être appelées avant qu’elles ne soient entièrement définies, ce qui permet par exemple de placer la fonction à partir de leur emplacement (principal) avant la définition même de ces fonctions.

Mais déclarer des fonctions avant d’être définies n’est pas seulement utile pour réorganiser l’ordre des fonctions dans le code. Dans certains cas, comme dans ce cas particulier, au moins une des déclarations est requise, car impair et pair sont appelés mutuellement; il y a un appel à pair dans impair et un appel à impair dans pair. Et, par conséquent, il n’ya aucun moyen de structurer le code de telle sorte que impair soit défini avant même, et même avant, impair.

Récursivité

La récursivité est la propriété que les fonctions doivent être appelées par elles-mêmes. Il est utile pour certaines tâches, telles que le tri des éléments ou le calcul de la factorielle des nombres. Par exemple, pour obtenir la factorielle d’un nombre (n!), La formule mathématique serait:

n! = n * (n-1) * (n-2) * (n-3) … * 1

Plus concrètement, 5! (factorielle de 5) serait:

5! = 5 * 4 * 3 * 2 * 1 = 120

Et une fonction récursive pour calculer cela en C ++ pourrait être:

// calculateur factoriel
#include <iostream>
using namespace std;

long factoriel (long a)
{
  if (a> 1)
   return (a * factoriel (a-1));
  else
   return 1;
}

int main ()
{
  nombre long = 9;
  cout << numéro << "! =" << factoriel (nombre);
  return 0;
}

Résultat de l’exécution :

9! = 362880

Remarquez comment, dans la fonction factorielle, nous avons inclus un appel à lui-même, mais uniquement si l’argument passé était supérieur à 1, car sinon, la fonction effectuerait une boucle récursive infinie, dans laquelle, une fois arrivée à 0, elle continuerait à se multiplier par tout. les nombres négatifs (provoquant probablement un débordement de pile à un moment donné au cours de l’exécution).




Catégorie : C++ Étiquettes :

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *