Définir et utiliser des fonctions en C++

C++Beginner
Pratiquer maintenant

Introduction

Dans ce laboratoire, vous plongerez dans le monde des fonctions en C++. Les fonctions sont une pierre angulaire de la programmation, vous permettant d'organiser votre code en blocs gérables et réutilisables. Ce laboratoire couvre un large éventail de sujets liés aux fonctions, de la création de fonctions avec différents types de retour à l'exploitation de concepts avancés tels que la récursivité et la surcharge de fonctions. Vous apprendrez à définir des fonctions, à passer des paramètres par valeur et par référence, à définir des valeurs de paramètres par défaut et à comprendre les prototypes de fonctions. De plus, vous découvrirez comment retourner plusieurs valeurs à l'aide de structures. À la fin de ce laboratoire, vous aurez une base solide pour écrire du code C++ modulaire, efficace et bien structuré.

Nous commencerons par explorer les fonctions avec différents types de retour, tels que les entiers, les nombres à virgule flottante, les caractères et les chaînes de caractères. Vous apprendrez ensuite la différence essentielle entre le passage de paramètres par valeur et par référence, et comment les valeurs de paramètres par défaut peuvent ajouter de la flexibilité à vos fonctions. Nous aborderons également la surcharge de fonctions, qui vous permet de définir plusieurs fonctions portant le même nom mais avec des listes de paramètres différentes. De plus, vous explorerez les fonctions récursives, un outil puissant pour résoudre des problèmes en s'appelant elles-mêmes. Vous verrez comment utiliser des prototypes de fonctions dans des fichiers d'en-tête pour créer un code plus organisé. Enfin, vous découvrirez comment retourner plusieurs valeurs à partir d'une fonction à l'aide de structures.

Créer des fonctions avec différents types de retour

Les fonctions sont un concept fondamental en programmation. Elles vous permettent de décomposer de grands programmes en morceaux plus petits et gérables, rendant votre code plus facile à écrire, à comprendre et à maintenir. Les fonctions peuvent également retourner des valeurs, permettant ainsi de renvoyer des données à la partie du programme qui a appelé la fonction.

Voici la syntaxe de base d'une fonction en C++ :

return_type function_name(parameters) {
    // Corps de la fonction
    return value;
}
  • return_type : Ceci spécifie le type de données de la valeur que la fonction retournera. Si la fonction ne retourne aucune valeur, vous utilisez le mot-clé void.
  • function_name : C'est le nom que vous utiliserez pour appeler la fonction. Choisissez un nom qui décrit clairement le but de la fonction.
  • parameters : Ce sont les valeurs d'entrée que vous passez à la fonction. Elles sont optionnelles, et vous pouvez avoir zéro ou plusieurs paramètres.
  • value : C'est la valeur que la fonction retourne. Elle doit correspondre au return_type de la fonction. Si le return_type est void, vous omettez l'instruction return ou utilisez simplement return; sans valeur.

Dans cette étape, vous apprendrez à créer des fonctions qui retournent différents types de données. Cette capacité est cruciale pour construire des programmes polyvalents et fonctionnels.

Ouvrez le WebIDE et créez un nouveau fichier nommé function_types.cpp dans le répertoire ~/project :

touch ~/project/function_types.cpp

Ouvrez le fichier function_types.cpp dans l'éditeur et ajoutez le code suivant :

#include <iostream>
#include <string>

// Fonction retournant un entier
int addNumbers(int a, int b) {
    return a + b;
}

// Fonction retournant un double (nombre à virgule flottante)
double calculateAverage(double x, double y) {
    return (x + y) / 2.0;
}

// Fonction retournant un caractère
char getGrade(int score) {
    if (score >= 90) return 'A';
    if (score >= 80) return 'B';
    if (score >= 70) return 'C';
    if (score >= 60) return 'D';
    return 'F';
}

// Fonction retournant une chaîne de caractères (string)
std::string getStudentStatus(bool isEnrolled) {
    return isEnrolled ? "Active" : "Inactive";
}

int main() {
    // Démonstration des différents types de retour
    int sum = addNumbers(5, 3);
    std::cout << "Sum: " << sum << std::endl;

    double average = calculateAverage(10.5, 20.5);
    std::cout << "Average: " << average << std::endl;

    char grade = getGrade(85);
    std::cout << "Grade: " << grade << std::endl;

    std::string status = getStudentStatus(true);
    std::cout << "Student Status: " << status << std::endl;

    return 0;
}

Compilez et exécutez le programme :

g++ function_types.cpp -o function_types
./function_types

Exemple de sortie :

Sum: 8
Average: 15.5
Grade: B
Student Status: Active

Points clés concernant les types de retour des fonctions :

  • Les fonctions peuvent retourner différents types de données, offrant ainsi de la flexibilité au code.
  • Le return_type est spécifié avant le nom de la fonction, indiquant quel type de données la fonction renverra.
  • L'instruction return doit correspondre au return_type déclaré, sinon le compilateur signalera une erreur.
  • Les fonctions void ne retournent pas de valeur, et vous utilisez return; ou omettez simplement l'instruction return si vous êtes à la fin d'une fonction void.
  • Vous pouvez retourner des types primitifs (comme int, double, char), des chaînes de caractères (strings), et même des types définis par l'utilisateur.
  • Pensez aux types de retour des fonctions comme à différents types de contenants. Tout comme vous utiliseriez une boîte, un sac ou un panier pour transporter différents objets, les fonctions utilisent différents types de retour pour renvoyer différents types de données.

Passer des paramètres aux fonctions par valeur

Dans cette étape, vous apprendrez à passer des paramètres aux fonctions par valeur en C++. Lorsque vous passez un paramètre par valeur, une copie de l'argument d'origine est créée, et cette copie est utilisée dans la fonction. Toute modification apportée au paramètre à l'intérieur de la fonction n'affecte pas la variable d'origine en dehors de la fonction.

Ouvrez le WebIDE et créez un nouveau fichier nommé pass_by_value.cpp dans le répertoire ~/project :

touch ~/project/pass_by_value.cpp

Ouvrez le fichier pass_by_value.cpp dans l'éditeur et ajoutez le code suivant :

#include <iostream>

// Fonction qui démontre le passage par valeur
void modifyValue(int x) {
    // Cette modification n'affecte que la copie locale, pas la variable d'origine
    x = x * 2;
    std::cout << "Inside function - Value of x: " << x << std::endl;
}

int main() {
    // Variable d'origine
    int number = 10;

    // Afficher la valeur d'origine
    std::cout << "Before function call - Original value: " << number << std::endl;

    // Appeler la fonction avec la variable d'origine
    modifyValue(number);

    // La variable d'origine reste inchangée
    std::cout << "After function call - Original value: " << number << std::endl;

    return 0;
}

Compilez et exécutez le programme :

g++ pass_by_value.cpp -o pass_by_value
./pass_by_value

Exemple de sortie :

Before function call - Original value: 10
Inside function - Value of x: 20
After function call - Original value: 10

Créons un autre exemple pour illustrer davantage le passage par valeur :

touch ~/project/swap_values.cpp

Ajoutez le code suivant à swap_values.cpp :

#include <iostream>
#include <string>

// Fonction pour échanger des valeurs (ne fonctionnera pas comme prévu avec le passage par valeur)
void swapValues(int a, int b) {
    int temp = a;
    a = b;
    b = temp;
    std::cout << "Inside function - a: " << a << ", b: " << b << std::endl;
}

int main() {
    int first = 5;
    int second = 10;

    std::cout << "Before swap - first: " << first << ", second: " << second << std::endl;

    // Appel de la fonction swap
    swapValues(first, second);

    // Les variables d'origine restent inchangées
    std::cout << "After swap - first: " << first << ", second: " << second << std::endl;

    return 0;
}

Compilez et exécutez le second programme :

g++ swap_values.cpp -o swap_values
./swap_values

Exemple de sortie :

Before swap - first: 5, second: 10
Inside function - a: 10, b: 5
After swap - first: 5, second: 10

Points clés concernant le passage par valeur :

  • Une copie de l'argument est passée à la fonction, préservant ainsi la valeur d'origine.
  • Les modifications apportées au paramètre à l'intérieur de la fonction n'affectent pas la variable d'origine en dehors de la fonction.
  • Le passage par valeur convient aux types de données simples comme int, char, float, etc., lorsque vous n'avez pas besoin de modifier la valeur d'origine.
  • Lors du passage de grands objets par valeur, la copie peut consommer plus de mémoire et de temps, ce qui la rend moins efficace que le passage par référence.

Vous pouvez considérer le passage par valeur comme la création d'une photocopie d'un document. L'original reste inchangé, et vous pouvez modifier la copie sans affecter l'original.

Implémenter le passage par référence avec l'opérateur &

Dans cette étape, vous apprendrez comment passer des paramètres aux fonctions par référence en utilisant l'opérateur & en C++. Le passage par référence permet de fournir à la fonction un accès direct à la variable d'origine, ce qui signifie que toute modification apportée au paramètre dans la fonction modifiera directement la variable d'origine en dehors de la fonction.

Ouvrez le WebIDE et créez un nouveau fichier nommé pass_by_reference.cpp dans le répertoire ~/project :

touch ~/project/pass_by_reference.cpp

Ouvrez le fichier pass_by_reference.cpp dans l'éditeur et ajoutez le code suivant :

#include <iostream>

// Fonction qui démontre le passage par référence
void swapValues(int& a, int& b) {
    // Modifier directement les variables d'origine
    int temp = a;
    a = b;
    b = temp;
}

// Fonction pour modifier une valeur en utilisant la référence
void incrementValue(int& x) {
    // Augmente directement la variable d'origine
    x++;
}

int main() {
    // Exemple d'échange
    int first = 5;
    int second = 10;

    std::cout << "Before swap - first: " << first << ", second: " << second << std::endl;

    // Appel de la fonction swap avec des références
    swapValues(first, second);

    std::cout << "After swap - first: " << first << ", second: " << second << std::endl;

    // Exemple d'incrémentation
    int number = 7;

    std::cout << "Before increment: " << number << std::endl;

    // Appel de la fonction increment avec une référence
    incrementValue(number);

    std::cout << "After increment: " << number << std::endl;

    return 0;
}

Compilez et exécutez le programme :

g++ pass_by_reference.cpp -o pass_by_reference
./pass_by_reference

Exemple de sortie :

Before swap - first: 5, second: 10
After swap - first: 10, second: 5
Before increment: 7
After increment: 8

Créons un autre exemple pour démontrer la puissance du passage par référence :

touch ~/project/string_reference.cpp

Ajoutez le code suivant à string_reference.cpp :

#include <iostream>
#include <string>

// Fonction pour modifier une chaîne de caractères (string) en utilisant la référence
void appendText(std::string& text) {
    text += " - Modified";
}

int main() {
    std::string message = "Hello, World!";

    std::cout << "Original message: " << message << std::endl;

    // Modifier la chaîne de caractères directement en utilisant la référence
    appendText(message);

    std::cout << "Modified message: " << message << std::endl;

    return 0;
}

Compilez et exécutez le second programme :

g++ string_reference.cpp -o string_reference
./string_reference

Exemple de sortie :

Original message: Hello, World!
Modified message: Hello, World! - Modified

Points clés concernant le passage par référence :

  • Utilisez & après le type pour créer un paramètre de référence (par exemple, int& a).
  • Modifie directement la variable d'origine, contrairement au passage par valeur qui utilise une copie.
  • Évite la surcharge liée à la copie de grands objets, ce qui la rend plus efficace pour les types de données complexes.
  • Utile lorsque vous avez besoin de modifier la valeur d'origine ou de travailler avec des types de données complexes sans copie.

Vous pouvez considérer le passage par référence comme le fait de donner le document original à quelqu'un au lieu d'une photocopie. Toutes les modifications qu'il apportera affecteront directement l'original.

Définir des valeurs par défaut pour les paramètres de fonction

Dans cette étape, vous apprendrez comment définir des valeurs par défaut pour les paramètres de fonction en C++. Les paramètres par défaut vous permettent d'attribuer des valeurs par défaut aux arguments de fonction, rendant vos fonctions plus polyvalentes et plus faciles à utiliser. Lorsqu'un appelant omet un paramètre avec une valeur par défaut, la valeur par défaut est automatiquement utilisée.

Ouvrez le WebIDE et créez un nouveau fichier nommé default_parameters.cpp dans le répertoire ~/project :

touch ~/project/default_parameters.cpp

Ouvrez le fichier default_parameters.cpp dans l'éditeur et ajoutez le code suivant :

#include <iostream>
#include <string>

// Fonction avec paramètres par défaut
void greetUser(std::string name = "Guest", int age = 0) {
    std::cout << "Hello, " << name << "!" << std::endl;

    if (age > 0) {
        std::cout << "You are " << age << " years old." << std::endl;
    }
}

// Autre exemple avec des paramètres par défaut pour le calcul
double calculateArea(double length = 1.0, double width = 1.0) {
    return length * width;
}

int main() {
    // Appel de la fonction sans arguments (utilise les valeurs par défaut)
    greetUser();

    // Appel de la fonction avec des arguments partiels
    greetUser("Alice");

    // Appel de la fonction avec tous les arguments
    greetUser("Bob", 25);

    // Démonstration du calcul de surface avec des paramètres par défaut
    std::cout << "\nArea calculations:" << std::endl;

    // Surface par défaut (1x1)
    std::cout << "Default area: " << calculateArea() << std::endl;

    // Surface avec un paramètre
    std::cout << "Area with length 5: " << calculateArea(5.0) << std::endl;

    // Surface avec les deux paramètres
    std::cout << "Area with length 5 and width 3: " << calculateArea(5.0, 3.0) << std::endl;

    return 0;
}

Compilez et exécutez le programme :

g++ default_parameters.cpp -o default_parameters
./default_parameters

Exemple de sortie :

Hello, Guest!
Hello, Alice!
Hello, Bob!
You are 25 years old.

Area calculations:
Default area: 1
Area with length 5: 5
Area with length 5 and width 3: 15

Points clés concernant les paramètres par défaut :

  • Les valeurs par défaut sont spécifiées dans la déclaration de la fonction, juste après le type du paramètre (par exemple, int age = 0).
  • Les paramètres avec des valeurs par défaut doivent se trouver à la fin de la liste des paramètres. Vous ne pouvez pas avoir de paramètres non par défaut après des paramètres par défaut.
  • Vous pouvez appeler la fonction avec moins d'arguments, et les arguments manquants utiliseront leurs valeurs par défaut.
  • Les valeurs par défaut offrent de la flexibilité et réduisent le besoin de surcharges de fonctions multiples qui font la même chose avec des entrées légèrement différentes.

Créons un autre exemple pour démontrer des paramètres par défaut plus complexes :

touch ~/project/student_info.cpp

Ajoutez le code suivant à student_info.cpp :

#include <iostream>
#include <string>

// Fonction avec plusieurs paramètres par défaut
void printStudentInfo(
    std::string name = "Unknown",
    int age = 0,
    std::string grade = "N/A",
    bool isEnrolled = false
) {
    std::cout << "Name: " << name << std::endl;
    std::cout << "Age: " << age << std::endl;
    std::cout << "Grade: " << grade << std::endl;
    std::cout << "Enrollment Status: " << (isEnrolled ? "Enrolled" : "Not Enrolled") << std::endl;
}

int main() {
    // Différentes manières d'appeler la fonction
    printStudentInfo();  // Tous les défauts
    std::cout << "\n";
    printStudentInfo("John");  // Seulement le nom
    std::cout << "\n";
    printStudentInfo("Alice", 20);  // Nom et âge
    std::cout << "\n";
    printStudentInfo("Bob", 22, "A", true);  // Tous les paramètres

    return 0;
}

Compilez et exécutez le second programme :

g++ student_info.cpp -o student_info
./student_info

Exemple de sortie :

Name: Unknown
Age: 0
Grade: N/A
Enrollment Status: Not Enrolled

Name: John
Age: 0
Grade: N/A
Enrollment Status: Not Enrolled

Name: Alice
Age: 20
Grade: N/A
Enrollment Status: Not Enrolled

Name: Bob
Age: 22
Grade: A
Enrollment Status: Enrolled

Vous pouvez considérer les paramètres par défaut comme un buffet où vous pouvez choisir de prendre tous les plats ou seulement quelques-uns, certains plats étant automatiquement remplis si vous ne les spécifiez pas.

Créer des surcharges de fonction avec des paramètres différents

Dans cette étape, vous découvrirez le concept de surcharge de fonctions (function overloading) en C++, une fonctionnalité qui vous permet de créer plusieurs fonctions portant le même nom, à condition qu'elles aient des listes de paramètres différentes (soit par le nombre de paramètres, soit par les types de paramètres). Cette fonctionnalité rend votre code plus flexible et lisible en vous permettant d'effectuer des opérations similaires sur différents types de données ou avec un nombre variable d'entrées en utilisant le même nom de fonction.

Ouvrez le WebIDE et créez un nouveau fichier nommé function_overloads.cpp dans le répertoire ~/project :

touch ~/project/function_overloads.cpp

Ouvrez le fichier function_overloads.cpp dans l'éditeur et ajoutez le code suivant :

#include <iostream>
#include <string>

// Surcharges de fonctions pour additionner différents types de nombres
int add(int a, int b) {
    std::cout << "Adding two integers" << std::endl;
    return a + b;
}

double add(double a, double b) {
    std::cout << "Adding two doubles" << std::endl;
    return a + b;
}

// Surcharges de fonctions avec un nombre différent de paramètres
int add(int a, int b, int c) {
    std::cout << "Adding three integers" << std::endl;
    return a + b + c;
}

// Surcharge de fonction avec des types de paramètres différents
std::string add(std::string a, std::string b) {
    std::cout << "Concatenating strings" << std::endl;
    return a + b;
}

int main() {
    // Appel de différentes fonctions surchargées
    std::cout << "Integer addition: " << add(5, 3) << std::endl;
    std::cout << "Double addition: " << add(5.5, 3.7) << std::endl;
    std::cout << "Three integer addition: " << add(1, 2, 3) << std::endl;
    std::cout << "String concatenation: " << add("Hello, ", "World!") << std::endl;

    return 0;
}

Compilez et exécutez le programme :

g++ function_overloads.cpp -o function_overloads
./function_overloads

Exemple de sortie :

Adding two integers
Integer addition: 8
Adding two doubles
Double addition: 9.2
Adding three integers
Three integer addition: 6
Concatenating strings
String concatenation: Hello, World!

Créons un autre exemple pour démontrer davantage la surcharge de fonctions :

touch ~/project/overloading_examples.cpp

Ajoutez le code suivant à overloading_examples.cpp :

#include <iostream>
#include <string>

// Fonctions print surchargées
void print(int value) {
    std::cout << "Printing integer: " << value << std::endl;
}

void print(double value) {
    std::cout << "Printing double: " << value << std::endl;
}

void print(std::string value) {
    std::cout << "Printing string: " << value << std::endl;
}

// Fonction calculate surchargée
int calculate(int a, int b) {
    return a + b;
}

double calculate(double a, double b) {
    return a * b;
}

int main() {
    // Démonstration de la surcharge de fonctions
    print(42);
    print(3.14);
    print("Hello, Overloading!");

    std::cout << "Integer calculation: " << calculate(5, 3) << std::endl;
    std::cout << "Double calculation: " << calculate(5.5, 3.2) << std::endl;

    return 0;
}

Compilez et exécutez le second programme :

g++ overloading_examples.cpp -o overloading_examples
./overloading_examples

Exemple de sortie :

Printing integer: 42
Printing double: 3.14
Printing string: Hello, Overloading!
Integer calculation: 8
Double calculation: 17.6

Points clés concernant la surcharge de fonctions :

  • Les fonctions peuvent avoir le même nom tant qu'elles ont des listes de paramètres différentes (types différents ou nombre de paramètres différents).
  • Le compilateur C++ décide quelle fonction surchargée appeler en fonction des types et du nombre d'arguments fournis lors de l'appel de la fonction.
  • La surcharge de fonctions permet d'obtenir un code plus lisible et intuitif, en vous permettant de réutiliser des noms pour des actions similaires.
  • Vous ne pouvez pas surcharger des fonctions si elles ne diffèrent que par leurs types de retour. Les paramètres doivent être différents pour que la surcharge fonctionne correctement.

Vous pouvez considérer la surcharge de fonctions comme une télécommande universelle qui fonctionne avec différents types d'appareils. Le même bouton (nom de fonction) effectue différentes actions en fonction du contexte (les arguments que vous fournissez).

Écrire des fonctions récursives pour le calcul de factorielle

Dans cette étape, vous découvrirez les fonctions récursives en implémentant un programme de calcul de factorielle. La récursivité est une technique de programmation où une fonction s'appelle elle-même pour résoudre un problème. Pour éviter les boucles infinies, les fonctions récursives doivent avoir un cas de base (base case) où elles arrêtent l'appel récursif, ainsi qu'un cas récursif (recursive case) pour continuer à s'appeler afin de progresser vers le cas de base.

Ouvrez le WebIDE et créez un nouveau fichier nommé factorial_recursive.cpp dans le répertoire ~/project :

touch ~/project/factorial_recursive.cpp

Ouvrez le fichier factorial_recursive.cpp dans l'éditeur et ajoutez le code suivant :

#include <iostream>

// Fonction récursive pour calculer la factorielle
unsigned long long calculateFactorial(int n) {
    // Cas de base : la factorielle de 0 ou 1 est 1
    if (n == 0 || n == 1) {
        return 1;
    }

    // Cas récursif : n! = n * (n-1)!
    return n * calculateFactorial(n - 1);
}

int main() {
    // Calcul de la factorielle pour différents nombres
    int numbers[] = {0, 1, 5, 7, 10};

    for (int num : numbers) {
        unsigned long long result = calculateFactorial(num);
        std::cout << "Factorial of " << num << " is: " << result << std::endl;
    }

    return 0;
}

Compilez et exécutez le programme :

g++ factorial_recursive.cpp -o factorial_recursive
./factorial_recursive

Exemple de sortie :

Factorial of 0 is: 1
Factorial of 1 is: 1
Factorial of 5 is: 120
Factorial of 7 is: 5040
Factorial of 10 is: 3628800

Créons un autre exemple avec une fonction récursive plus détaillée :

touch ~/project/factorial_steps.cpp

Ajoutez le code suivant à factorial_steps.cpp :

#include <iostream>

// Fonction récursive avec sortie étape par étape
unsigned long long calculateFactorialWithSteps(int n, int depth = 0) {
    // Ajout d'une indentation pour la visualisation
    std::string indent(depth * 2, ' ');

    // Cas de base : la factorielle de 0 ou 1 est 1
    if (n == 0 || n == 1) {
        std::cout << indent << "Base case: factorial(" << n << ") = 1" << std::endl;
        return 1;
    }

    // Cas récursif avec visualisation
    std::cout << indent << "Calculating factorial(" << n << ")" << std::endl;

    // Appel récursif
    unsigned long long subResult = calculateFactorialWithSteps(n - 1, depth + 1);

    // Combinaison des résultats
    unsigned long long result = n * subResult;

    std::cout << indent << "factorial(" << n << ") = "
              << n << " * factorial(" << n-1 << ") = "
              << result << std::endl;

    return result;
}

int main() {
    int number = 5;

    std::cout << "Factorial calculation steps for " << number << ":" << std::endl;
    unsigned long long result = calculateFactorialWithSteps(number);

    std::cout << "\nFinal result: " << number << "! = " << result << std::endl;

    return 0;
}

Compilez et exécutez le second programme :

g++ factorial_steps.cpp -o factorial_steps
./factorial_steps

Exemple de sortie :

Factorial calculation steps for 5:
Calculating factorial(5)
  Calculating factorial(4)
    Calculating factorial(3)
      Calculating factorial(2)
        Calculating factorial(1)
        Base case: factorial(1) = 1
      factorial(2) = 2 * factorial(1) = 2
    factorial(3) = 3 * factorial(2) = 6
  factorial(4) = 4 * factorial(3) = 24
factorial(5) = 5 * factorial(4) = 120

Final result: 5! = 120

Points clés concernant les fonctions récursives :

  • Les fonctions récursives s'appellent elles-mêmes : Une fonction s'appelle elle-même pour résoudre des sous-problèmes plus petits du même type.
  • Cas de base (Base Case) : Une fonction récursive doit avoir un cas de base, une condition qui arrête les appels récursifs. Sans cas de base, la fonction s'appellera elle-même à l'infini, ce qui entraînera une erreur de dépassement de pile (stack overflow).
  • Cas récursif (Recursive Case) : Le cas récursif est l'endroit où la fonction s'appelle elle-même pour progresser vers le cas de base.
  • Décomposition du problème : La récursivité décompose un problème complexe en sous-problèmes plus petits et plus simples, ce qui facilite sa gestion.
  • Utilisation de la pile (Stack usage) : Chaque appel récursif consomme de l'espace sur la pile d'appels du programme. Une récursivité profonde peut entraîner un dépassement de pile, il faut donc être conscient des limites de la pile d'appels.
  • Problèmes adaptés : La récursivité est particulièrement bien adaptée aux problèmes qui peuvent être naturellement définis en termes de sous-problèmes plus petits et similaires, tels que les parcours d'arbres, les algorithmes de type "diviser pour régner" (divide-and-conquer) et la génération de fractales.

Vous pouvez penser à la récursivité comme à un ensemble de poupées russes imbriquées, où chaque poupée contient une version plus petite d'elle-même, et la poupée la plus intérieure représente le cas de base qui arrête le processus d'imbrication.

Utiliser des prototypes de fonction dans les fichiers d'en-tête

Dans cette étape, vous découvrirez les prototypes de fonctions et comment utiliser les fichiers d'en-tête pour organiser et déclarer des fonctions en C++. Les prototypes de fonctions déclarent le nom, le type de retour et les paramètres d'une fonction sans fournir l'implémentation (le corps de la fonction). En utilisant des fichiers d'en-tête, vous séparez l'interface d'une fonction (sa déclaration) de son implémentation. Cette séparation améliore l'organisation du code, facilitant la maintenance et la réutilisation des fonctions dans plusieurs parties de votre programme ou dans plusieurs programmes.

Ouvrez le WebIDE et créez trois fichiers dans le répertoire ~/project :

Premièrement, créez math_functions.h :

touch ~/project/math_functions.h

Ajoutez le code suivant à math_functions.h :

#ifndef MATH_FUNCTIONS_H
#define MATH_FUNCTIONS_H

// Prototypes de fonctions pour les opérations mathématiques
int add(int a, int b);
int subtract(int a, int b);
int multiply(int a, int b);
double divide(double a, double b);

#endif // MATH_FUNCTIONS_H

Les fichiers .h sont utilisés pour les déclarations, contenant des prototypes de fonctions et d'autres déclarations mais pas les implémentations. De cette façon, vous pouvez déclarer des fonctions sans les implémenter. Les directives #ifndef, #define et #endif sont appelées gardes d'inclusion (include guards), et elles empêchent les inclusions multiples du fichier d'en-tête, ce qui peut causer des erreurs.

Ensuite, créez math_functions.cpp :

touch ~/project/math_functions.cpp

Ajoutez le code suivant à math_functions.cpp :

#include "math_functions.h"

// Implémentations des fonctions
int add(int a, int b) {
    return a + b;
}

int subtract(int a, int b) {
    return a - b;
}

int multiply(int a, int b) {
    return a * b;
}

double divide(double a, double b) {
    // Vérification de la division par zéro
    if (b == 0) {
        return 0;
    }
    return a / b;
}

Ce fichier .cpp contient les implémentations réelles des fonctions qui ont été déclarées dans le fichier d'en-tête.

Enfin, créez main.cpp :

touch ~/project/main.cpp

Ajoutez le code suivant à main.cpp :

#include <iostream>
#include "math_functions.h"

int main() {
    // Démonstration des appels de fonctions depuis le fichier d'en-tête
    int x = 10, y = 5;

    std::cout << "Addition: " << x << " + " << y << " = " << add(x, y) << std::endl;
    std::cout << "Subtraction: " << x << " - " << y << " = " << subtract(x, y) << std::endl;
    std::cout << "Multiplication: " << x << " * " << y << " = " << multiply(x, y) << std::endl;
    std::cout << "Division: " << x << " / " << y << " = " << divide(x, y) << std::endl;

    return 0;
}

Ce fichier main.cpp inclut le fichier d'en-tête math_functions.h, ce qui rend les prototypes de fonctions disponibles. Il peut ensuite utiliser les fonctions implémentées dans math_functions.cpp.

Compilez le programme en utilisant plusieurs fichiers source :

g++ math_functions.cpp main.cpp -o math_operations
./math_operations

Exemple de sortie :

Addition: 10 + 5 = 15
Subtraction: 10 - 5 = 5
Multiplication: 10 * 5 = 50
Division: 10 / 5 = 2

Créons un autre exemple avec un fichier d'en-tête plus complexe :

Créez calculator.h :

touch ~/project/calculator.h

Ajoutez le code suivant à calculator.h :

#ifndef CALCULATOR_H
#define CALCULATOR_H

class Calculator {
public:
    // Prototypes de fonctions pour les opérations de calculatrice
    int add(int a, int b);
    int subtract(int a, int b);
    int calculate(int a, int b, char operation);
};

#endif // CALCULATOR_H

Ce fichier d'en-tête déclare une classe nommée Calculator et ses méthodes publiques.

Créez calculator.cpp :

touch ~/project/calculator.cpp

Ajoutez le code suivant à calculator.cpp :

#include "calculator.h"

int Calculator::add(int a, int b) {
    return a + b;
}

int Calculator::subtract(int a, int b) {
    return a - b;
}

int Calculator::calculate(int a, int b, char operation) {
    switch (operation) {
        case '+': return add(a, b);
        case '-': return subtract(a, b);
        default: return 0;
    }
}

Ce calculator.cpp fournit les implémentations des méthodes déclarées dans le fichier d'en-tête calculator.h.

Créez calculator_main.cpp :

touch ~/project/calculator_main.cpp

Ajoutez le code suivant à calculator_main.cpp :

#include <iostream>
#include "calculator.h"

int main() {
    Calculator calc;

    std::cout << "Calculator Operations:" << std::endl;
    std::cout << "10 + 5 = " << calc.calculate(10, 5, '+') << std::endl;
    std::cout << "10 - 5 = " << calc.calculate(10, 5, '-') << std::endl;

    return 0;
}

Ce fichier principal utilise la classe Calculator et effectue des opérations.

Compilez le programme de calculatrice :

g++ calculator.cpp calculator_main.cpp -o calculator
./calculator

Exemple de sortie :

Calculator Operations:
10 + 5 = 15
10 - 5 = 5

Points clés concernant les prototypes de fonctions et les fichiers d'en-tête :

  • Les fichiers d'en-tête (.h) déclarent les prototypes de fonctions, les classes et d'autres déclarations. Ils agissent comme une interface, décrivant quelles fonctions sont disponibles.
  • Les fichiers source (.cpp) implémentent les fonctions réelles déclarées dans les fichiers d'en-tête. Ils contiennent le code expliquant le fonctionnement des fonctions.
  • #ifndef, #define et #endif (gardes d'inclusion) empêchent les inclusions multiples du même fichier d'en-tête, ce qui évite les erreurs de compilation potentielles.
  • L'utilisation de fichiers d'en-tête favorise la modularité et la réutilisabilité du code.
  • Les fichiers d'en-tête vous permettent de séparer le "quoi" (déclarations) du "comment" (implémentations).
  • Ils facilitent l'organisation du code, le rendant plus facile à maintenir et à comprendre.

Vous pouvez considérer les fichiers d'en-tête comme le menu d'un restaurant. Le menu (en-tête) liste ce qui est disponible, tandis que la cuisine (fichier source) prépare les plats réels.

Retourner plusieurs valeurs à l'aide de structures

Dans cette étape, vous apprendrez à utiliser des structures pour retourner plusieurs valeurs à partir d'une fonction en C++. Les structures sont des types de données définis par l'utilisateur qui vous permettent de regrouper différents types de données sous un seul nom. Elles sont idéales pour retourner plusieurs valeurs liées à partir d'une fonction de manière structurée et organisée.

Ouvrez le WebIDE et mettez à jour le fichier student_info.cpp dans le répertoire ~/project :

Remplacez le code existant par le suivant :

#include <iostream>
#include <string>

// Définir une structure pour contenir les informations de l'étudiant
struct StudentResult {
    std::string name;
    int score;
    bool passed;
};

// Fonction qui retourne plusieurs valeurs à l'aide d'une structure
StudentResult evaluateStudent(std::string name, int testScore) {
    StudentResult result;
    result.name = name;
    result.score = testScore;
    result.passed = (testScore >= 60);

    return result;
}

// Fonction pour calculer plusieurs valeurs statistiques
struct MathStats {
    int sum;
    double average;
    int minimum;
    int maximum;
};

MathStats calculateArrayStats(int numbers[], int size) {
    MathStats stats;
    stats.sum = 0;
    stats.minimum = numbers[0];
    stats.maximum = numbers[0];

    for (int i = 0; i < size; i++) {
        stats.sum += numbers[i];

        if (numbers[i] < stats.minimum) {
            stats.minimum = numbers[i];
        }

        if (numbers[i] > stats.maximum) {
            stats.maximum = numbers[i];
        }
    }

    stats.average = static_cast<double>(stats.sum) / size;

    return stats;
}

int main() {
    // Démonstration de l'évaluation des étudiants
    StudentResult student1 = evaluateStudent("Alice", 75);
    StudentResult student2 = evaluateStudent("Bob", 45);

    std::cout << "Student Results:" << std::endl;
    std::cout << student1.name << " - Score: " << student1.score
              << ", Passed: " << (student1.passed ? "Yes" : "No") << std::endl;
    std::cout << student2.name << " - Score: " << student2.score
              << ", Passed: " << (student2.passed ? "Yes" : "No") << std::endl;

    // Démonstration des statistiques de tableau
    int numbers[] = {5, 2, 9, 1, 7};
    int size = sizeof(numbers) / sizeof(numbers[0]);

    MathStats stats = calculateArrayStats(numbers, size);

    std::cout << "\nArray Statistics:" << std::endl;
    std::cout << "Sum: " << stats.sum << std::endl;
    std::cout << "Average: " << stats.average << std::endl;
    std::cout << "Minimum: " << stats.minimum << std::endl;
    std::cout << "Maximum: " << stats.maximum << std::endl;

    return 0;
}

Compilez et exécutez le programme :

g++ student_info.cpp -o student_info
./student_info

Exemple de sortie :

Student Results:
Alice - Score: 75, Passed: Yes
Bob - Score: 45, Passed: No

Array Statistics:
Sum: 24
Average: 4.8
Minimum: 1
Maximum: 9

Points clés concernant les structures et le retour de plusieurs valeurs :

  • Les structures regroupent des données liées : Vous pouvez regrouper des variables de différents types (par exemple, string, int, bool) en une seule unité, ce qui peut être bénéfique lorsque vous avez des informations connexes.
  • Retour de types de données complexes : En utilisant des structures, vous pouvez retourner des types de données complexes à partir de fonctions, ce qui facilite la gestion et le passage d'ensembles de données liés.
  • Valeurs de retour organisées : Le retour de structures permet d'organiser plusieurs valeurs de retour liées, rendant le code plus propre et plus facile à maintenir.
  • Fournit un moyen clair de passer des informations structurées : Les structures fournissent un conteneur clair et nommé pour retourner plusieurs valeurs liées, améliorant la lisibilité et la compréhension du code.

Vous pouvez considérer les structures comme une boîte à lunch à plusieurs compartiments qui peut contenir différents types d'aliments dans des sections séparées.

Résumé

Dans ce laboratoire, vous avez acquis une compréhension approfondie de la manière de définir et d'utiliser des fonctions en C++. Vous avez appris à créer des fonctions avec différents types de retour, à passer des paramètres par valeur et par référence, à définir des valeurs de paramètres par défaut et à utiliser la surcharge de fonctions. De plus, vous avez exploré les fonctions récursives, comment utiliser des prototypes de fonctions dans les fichiers d'en-tête et comment retourner plusieurs valeurs à l'aide de structures.

Les concepts clés abordés comprennent :

  • Types de retour de fonction : Vous avez appris que les fonctions peuvent retourner différents types de données, et que l'instruction return doit correspondre au type de retour déclaré.
  • Passage par valeur et passage par référence : Vous avez compris la différence entre passer des paramètres par valeur (créer une copie) et par référence (utiliser la variable d'origine).
  • Paramètres par défaut : Vous avez appris à rendre les fonctions plus polyvalentes en attribuant des valeurs par défaut aux paramètres de fonction.
  • Surcharge de fonctions : Vous avez vu comment définir plusieurs fonctions portant le même nom mais avec des listes de paramètres différentes, rendant le code plus lisible et intuitif.
  • Fonctions récursives : Vous avez exploré la puissance des fonctions récursives, où une fonction s'appelle elle-même pour résoudre des sous-problèmes plus petits, ainsi que l'importance des cas de base pour éviter la récursion infinie.
  • Prototypes de fonctions et fichiers d'en-tête : Vous avez appris à utiliser les fichiers d'en-tête pour organiser les déclarations de fonctions, favorisant la modularité et la réutilisabilité du code.
  • Retour de plusieurs valeurs avec des structures : Vous avez découvert comment retourner plusieurs valeurs liées à partir d'une fonction à l'aide de structures, qui fournissent un conteneur organisé pour les données.

En maîtrisant ces concepts, vous êtes maintenant bien équipé pour écrire du code C++ plus modulaire, efficace et bien structuré, et vous avez une base solide pour explorer des techniques de programmation plus avancées.