web-dev-qa-db-fra.com

Comment structurer une boucle qui se répète jusqu'au succès et gère les échecs

Je suis un programmeur autodidacte. J'ai commencé à programmer il y a environ 1,5 an. Maintenant, j'ai commencé à avoir des cours de programmation à l'école. Nous avons des cours de programmation depuis 1/2 an et nous en aurons un autre en ce moment.

Dans les cours, nous apprenons à programmer en C++ (qui est un langage que je savais déjà assez bien utiliser avant de commencer).

Je n'ai pas eu de difficultés pendant ce cours mais il y a un problème récurrent auquel je n'ai pas pu trouver de solution claire.

Le problème est comme ça (en pseudocode):

 do something
 if something failed:
     handle the error
     try something (line 1) again
 else:
     we are done!

Voici un exemple en C++

Le code invite l'utilisateur à entrer un nombre et le fait jusqu'à ce que l'entrée soit valide. Il utilise cin.fail() pour vérifier si l'entrée n'est pas valide. Lorsque cin.fail() est true je dois appeler cin.clear() et cin.ignore() pour pouvoir continuer à obtenir des entrées du flux.

Je suis conscient que ce code ne vérifie pas EOF. Les programmes que nous avons écrits ne devraient pas le faire.

Voici comment j'ai écrit le code dans l'une de mes tâches à l'école:

for (;;) {
    cout << ": ";
    cin >> input;
    if (cin.fail()) {
        cin.clear();
        cin.ignore(512, '\n');
        continue;
    }
    break;
}

Mon professeur a dit que je ne devrais pas utiliser break et continue comme ça. Il a suggéré que j'utilise à la place une boucle régulière while ou do ... while.

Il me semble que l'utilisation de break et continue est la manière la plus simple de représenter ce type de boucle. J'y ai réfléchi pendant un bon moment, mais je n'ai pas vraiment trouvé de solution plus claire.

Je pense qu'il voulait que je fasse quelque chose comme ça:

do {
    cout << ": ";
    cin >> input;
    bool fail = cin.fail();
    if (fail) {
        cin.clear();
        cin.ignore(512, '\n');
    }
} while (fail);

Pour moi, cette version semble beaucoup plus complexe car maintenant nous avons également une variable appelée fail pour garder une trace et la vérification de l'échec d'entrée est effectuée deux fois au lieu d'une seule fois.

J'ai aussi pensé que je pouvais écrire le code comme ça (abuser de l'évaluation des courts-circuits):

do {
    cout << ": ";
    cin >> input;
    if (fail) {
        cin.clear();
        cin.ignore(512, '\n');
    }
} while (cin.fail() && (cin.clear(), cin.ignore(512, '\n', true);

Cette version fonctionne exactement comme les autres. Il n'utilise pas break ou continue et le test cin.fail() n'est effectué qu'une seule fois. Il ne me semble cependant pas juste d'abuser de la "règle d'évaluation des courts-circuits" comme celle-ci. Je ne pense pas que mon professeur le souhaiterait non plus.

Ce problème ne s'applique pas seulement à la vérification de cin.fail(). J'ai utilisé break et continue comme ceci pour de nombreux autres cas qui impliquent de répéter un ensemble de code jusqu'à ce qu'une condition soit remplie où quelque chose doit également être fait si la condition n'est pas remplie (comme appeler cin.clear() et cin.ignore(...) dans l'exemple cin.fail()).

J'ai continué à utiliser break et continue tout au long du cours et maintenant mon professeur a cessé de s'en plaindre.

Quelles sont vos opinions à ce sujet?

Pensez-vous que mon professeur a raison?

Connaissez-vous une meilleure façon de représenter ce genre de problème?

8
wefwefa3

J'écrirais l'instruction if légèrement différente, donc elle est prise lorsque l'entrée est réussie.

for (;;) {
    cout << ": ";
    if (cin >> input)
        break;
    cin.clear();
    cin.ignore(512, '\n');
}

C'est aussi plus court.

Ce qui suggère une manière plus courte qui pourrait être appréciée par votre professeur:

cout << ": ";
while (!(cin >> input)) {
    cin.clear();
    cin.ignore(512, '\n');
    cout << ": ";
}
15
Sjoerd

Ce que vous devez viser, c'est en évitant les boucles brutes .

Déplacez la logique complexe dans une fonction d'aide et soudain, les choses sont beaucoup plus claires:

bool getValidUserInput(string & input)
{
    cout << ": ";
    cin >> input;
    if (cin.fail()) {
        cin.clear();
        cin.ignore(512, '\n');
        return false;
    }
    return true;
}

int main() {
    string input;
    while (!getValidUserInput(input)) { 
        // We wait for a valid user input...
    }
}
15
glampert

Ce n'est pas tant que for(;;) est mauvais. Ce n'est pas aussi clair que des modèles comme:

while (cin.fail()) {
    ...
}

Ou, comme l'a dit Sjoerd:

while (!(cin >> input)) {
    ...
}

Considérons le public principal pour ce genre de choses comme étant vos collègues programmeurs, y compris la future version de vous-même qui ne se souvient plus pourquoi vous avez bloqué la pause à la fin comme ça, ou sauté la pause avec la suite. Ce modèle de votre exemple:

for (;;) {
    ...
    if (blah) {
        continue;
    }
    break;
}

... nécessite quelques secondes de réflexion supplémentaire pour simuler par rapport à d'autres modèles. Ce n'est pas une règle stricte et rapide, mais sauter l'instruction break avec la continuation semble désordonné ou intelligent sans être utile, et placer l'instruction break à la fin de la boucle, même si cela fonctionne, est inhabituel. Normalement, les deux break et continue sont utilisés pour prématurément évacuer la boucle ou cette itération de la boucle, donc voir l'un ou l'autre à la fin semble un peu bizarre même si ce n'est pas.

A part ça, bonnes choses!

9
jdevlin

Mon professeur de logique à l'école a toujours dit, et je l'ai martelé dans mon cerveau, qu'il ne devrait y avoir qu'une seule entrée et une seule sortie pour les boucles. Sinon, vous commencez à obtenir du code spaghetti et à ne pas savoir où va le code pendant le débogage.

Dans la vraie vie, je pense que la lisibilité du code est vraiment importante, de sorte que si quelqu'un d'autre a besoin de résoudre ou de déboguer un problème avec votre code, il est facile pour eux de le faire.

De plus, s'il s'agit d'un problème de performances, car vous exécutez ce look des millions de fois, la boucle for peut être plus rapide. Les tests répondraient à cette question.

Je ne connais pas C++ mais j'ai complètement compris les deux premiers de vos exemples de code. Je suppose que continue va à la fin de la boucle for puis la boucle à nouveau. L'exemple while que j'ai complètement compris, mais pour moi, il était plus facile à comprendre que votre exemple for (;;). Le troisième, je devrais faire un travail de réflexion pour le comprendre.

Donc, pour moi, ce qui était plus facile à lire (ne connaissant pas le c ++) et ce que mon professeur de logique a dit que j'utiliserais la boucle while.

3
Jaydel Gluckie