Les sauts

Dans les chapitres précédents, nous avons vu comment modifier l’exécution de notre programme en fonction du résultat d’une ou plusieurs conditions. Ainsi, nous avons pu réaliser des tâches plus complexes que de simplement exécuter une suite d’instructions de manière linéaire.

Cette exécution non linéaire est possible grâce à ce que l’on appelle des sauts. Un saut correspond au passage d’un point à un autre d’un programme. Bien que cela vous ait été caché, sachez que vous en avez déjà rencontré ! En effet, une instruction if réalise par exemple un saut à votre insu.

if (/* Condition */)
{
    /* Bloc */
}

/* Suite du programme */

Dans le cas où la condition est fausse, l’exécution du programme passe le bloc de l’instruction if et exécute ce qui suit. Autrement dit, il y a un saut jusqu’à la suite du bloc.

Dans la même veine, une boucle while réalise également des sauts.

while (/* Condition */)
{
    /* Bloc à répéter */
}

/* Suite du programme */

Dans cet exemple, si la condition est vraie, le bloc qui suit est exécuté puis il y a un saut pour revenir à l’évaluation de la condition. Si en revanche elle est fausse, comme pour l’instruction if, il y a un saut au-delà du bloc d’instructions.

Tous ces sauts sont cependant automatiques et vous sont cachés. Dans ce chapitre, nous allons voir comment réaliser manuellement des sauts à l’aide de trois instructions : break, continue et goto.

L'instruction break

Nous avons déjà rencontré l’instruction break lors de la présentation de l’instruction switch, cette dernière permettait de quitter le bloc d’un switch pour reprendre immédiatement après. Cependant, l’instruction break peut également être utilisée au sein d’une boucle pour stopper son exécution (autrement dit pour effectuer un saut au-delà du bloc à répéter).

Exemple

Le plus souvent, une instruction break est employée pour sortir d’une itération lorsqu’une condition (différente de celle contrôlant l’exécution de la boucle) est remplie. Par exemple, si nous souhaitons réaliser un programme qui détermine le plus petit diviseur commun de deux nombres, nous pouvons utiliser cette instruction comme suit.

#include <stdio.h>


int main(void)
{
    int a;
    int b;

    printf("Entrez deux nombres : ");
    scanf("%d %d", &a, &b);

    int min = (a < b) ? a : b;

    for (int i = 2; i <= min; ++i)
        if (a % i == 0 && b % i == 0)
        {
            printf("le plus petit diviseur de %d et %d est %d\n", a, b, i);
            break;
        }

    return 0;
}
Résultat
Entrez deux nombres : 112 567
le plus petit diviseur de 112 et 567 est 7

Entrez deux nombres : 13 17

Comme vous le voyez, la condition principale permet de progresser parmi les diviseurs possibles alors que la seconde détermine si la valeur courante de i est un diviseur commun. Si c’est le cas, l’exécution de la boucle est stoppée et le résultat affiché. Dans le cas où il n’y a aucun diviseur commun, la boucle s’arrête lorsque le plus petit des deux nombres est atteint.

L’instruction continue

L’instruction continue permet d’arrêter l’exécution de l’itération courante. Autrement dit, celle-ci vous permet de retourner (sauter) directement à l’évaluation de la condition.

Exemple

Afin d’améliorer un peu l’exemple précédent, nous pourrions passer les cas où le diviseur testé est un multiple de deux (puisque si un des deux nombres n’est pas divisible par deux, il ne peut pas l’être par quatre, par exemple).

Ceci peut s’exprimer à l’aide de l’instruction continue.

#include <stdio.h>


int main(void)
{
    int a;
    int b;

    printf("Entrez deux nombres : ");
    scanf("%d %d", &a, &b);

    int min = (a < b) ? a : b;

    for (int i = 2; i <= min; ++i)
    {
        if (i != 2 && i % 2 == 0)
        {
            printf("je passe %d\n", i);
            continue;
        }
        if (a % i == 0 && b % i == 0)
        {
            printf("le plus petit diviseur de %d et %d est %d\n", a, b, i);
            break;
        }
    }

    return 0;
}
Résultat
je passe 4
je passe 6
le plus petit diviseur de 112 et 567 est 7

Dans le cas de la boucle for, l’exécution reprend à l’évaluation de sa deuxième expression (ici ++i) et non à l’évaluation de la condition (qui a lieu juste après). Il serait en effet mal venu que la variable i ne soit pas incrémentée lors de l’utilisation de l’instruction continue.

Boucles imbriquées

Notez bien que les instructions break et continue n’affectent que l’exécution de la boucle dans laquelle elles sont situées. Ainsi, si vous utilisez l’instruction break dans une boucle imbriquée dans une autre, vous sortirez de la première, mais pas de la seconde.

#include <stdio.h>


int main(void)
{
    for (int i = 0 ; i <= 1000 ; ++i)
        for (int j = i ; j <= 1000 ; ++j)
            if (i * j == 1000) 
            {
                printf ("%d * %d = 1000 \n", i, j);
                break; /* Quitte la boucle courante, mais pas la première. */
            }

    return 0;
}
Résultat
1 * 1000 = 1000 
2 * 500 = 1000 
4 * 250 = 1000 
5 * 200 = 1000 
8 * 125 = 1000 
10 * 100 = 1000 
20 * 50 = 1000 
25 * 40 = 1000 

L'instruction goto

Nous venons de voir qu’il était possible de réaliser des sauts à l’aide des instructions break et continue. Cependant, d’une part ces instructions sont confinées à une boucle ou à une instruction switch et, d’autre part, la destination du saut nous est imposée (la condition avec continue, la fin du bloc d’instructions avec break).

L’instruction goto permet de sauter à un point précis du programme que nous aurons déterminé à l’avance. Pour ce faire, le langage C nous permet de marquer des instructions à l’aide d’étiquettes (labels en anglais). Une étiquette n’est rien d’autre qu’un nom choisi par nos soins suivi du caractère :. Généralement, par souci de lisibilité, les étiquettes sont placées en retrait des instructions qu’elles désignent.

Exemple

Reprenons (encore) l’exemple du calcul du plus petit commun diviseur. Ce dernier aurait pu être écrit comme suit à l’aide d’une instruction goto.

#include <stdio.h>


int main(void)
{
    int a;
    int b;

    printf("Entrez deux nombres : ");
    scanf("%d %d", &a, &b);

    int min = (a < b) ? a : b;
    int i;

    for (i = 2; i <= min; ++i)
        if (a % i == 0 && b % i == 0)
            goto trouve;

    return 0;
trouve:
    printf("le plus petit diviseur de %d et %d est %d\n", a, b, i);
    return 0;
}

Comme vous le voyez, l’appel à la fonction printf() a été marqué avec une étiquette nommée trouve. Celle-ci est utilisée avec l’instruction goto pour spécifier que c’est à cet endroit que nous souhaitons nous rendre si un diviseur commun est trouvé. Vous remarquerez également que nous avons désormais deux instructions return, la première étant exécutée dans le cas où aucun diviseur commun n’est trouvé.

Le dessous des boucles

Maintenant que vous savez cela, vous devriez être capable de réécrire n’importe quelle boucle à l’aide de cette instruction. En effet, une boucle se compose de deux sauts : un vers une condition et l’autre vers l’instruction qui suit le corps de la boucle. Ainsi, les deux codes suivants sont équivalents.

#include <stdio.h>


int main(void)
{
    int i = 0;

    while (i < 5)
    {
        printf("La variable i vaut %d\n", i);
        i++;
    }

    return 0;
}
#include <stdio.h>


int main(void)
{
    int i = 0;

condition:
    if (i < 5)
    {
        printf("La variable i vaut %d\n", i);
        i++;
        goto condition;
    }

    return 0;
}
Goto Hell ?

Bien qu’utile dans certaines circonstances, sachez que l’instruction goto est fortement décriée, principalement pour deux raisons :

  • mis à part dans des cas spécifiques, il est possible de réaliser la même action de manière plus claire à l’aide de structures de contrôles ;
  • l’utilisation de cette instruction peut amener votre code à être plus difficilement lisible et, dans les pires cas, en faire un code spaghetti.

À vrai dire, elle est aujourd’hui surtout utilisée dans le cas de la gestion d’erreur, ce que nous verrons plus tard dans ce cours. Aussi, en attendant, nous vous déconseillons de l’utiliser.


Dans le chapitre suivant, nous aborderons la notion de fonction.

En résumé
  1. Une instruction de saut permet de passer directement à l’exécution d’une instruction précise ;
  2. L’instruction break permet de sortir de la boucle ou de l’instruction switch dans laquelle elle est employée ;
  3. L’instruction continue permet de retourner à l’évaluation de la condition de la boucle dans laquelle elle est utilisée ;
  4. L’instruction goto permet de passer à l’exécution d’une instruction désignée par une étiquette.