Cette page vous donne les différences entre la révision choisie et la version actuelle de la page.
boucles [2016/12/01 01:19] gbdivers |
boucles [2016/12/02 00:46] (Version actuelle) gbdivers |
||
---|---|---|---|
Ligne 321: | Ligne 321: | ||
Les syntaxes précédentes sont les plus sûres, mais si vous recherchez des exemples de code sur Internet, vous les rencontrerez moins souvent que les syntaxes qui suivent. Cela s'explique par le fait que les boucles //range-for// sont un ajout "récent" du C++11 (donc plus de 5 ans...), que les algorithmes standards s'utilisent régulièrement avec les fonctions //lambdas// (qui sont egalement un ajout du C++11), et que ces deux syntaxes n'existent pas en C. | Les syntaxes précédentes sont les plus sûres, mais si vous recherchez des exemples de code sur Internet, vous les rencontrerez moins souvent que les syntaxes qui suivent. Cela s'explique par le fait que les boucles //range-for// sont un ajout "récent" du C++11 (donc plus de 5 ans...), que les algorithmes standards s'utilisent régulièrement avec les fonctions //lambdas// (qui sont egalement un ajout du C++11), et que ces deux syntaxes n'existent pas en C. | ||
+ | Dans les syntaxes précédentes, les trois composantes d'une itération (initialisation, incrémentation et condition d'arrêt) sont implicites, ce qui évite de faire des erreurs en les écrivant. La boucle //for// permet d'ecrire explicitement ces trois composantes. La syntaxe générale est la suivante : | ||
+ | <code> | ||
+ | for (INITIALISATION; TEST_CONTINUATION; INCREMENTATION) { | ||
+ | ... | ||
+ | } | ||
+ | </code> | ||
+ | |||
+ | Une particularité de la boucle //for// : il s'agit d'un test de continuation, et non un test d'arret. C'est-à-dire que la boucle continuera tant que la condition est vraie (au lieu de s'arrêter dès que la condition est vraie). | ||
+ | |||
+ | Par exemple, pour compter de 1 à 10, il faut : | ||
+ | |||
+ | * initialiser une variable avec la valeur 1 ; | ||
+ | * incrementer de 1 cette variable ; | ||
+ | * tant que la variable est inférieure ou égale à la valeur 10, continuer la boucle. | ||
- | 3 éléments, tous optionnels : | + | Chacune de ces composants sont facilement traduit en C++ : |
- | * initialisation | + | * initialisation : ''int i { 1 }'' ; |
- | * test de continuation | + | * incrementation : ''++i'' ; |
- | * incrémentation | + | * condition d'arret : ''i <= 10''. |
- | syntaxe : | + | En utilisant ces syntaxes dans un boucle //for//, vous obtenez donc : |
<code> | <code> | ||
- | for (INITIALISATION; TEST_CONTINUATION; INCREMENTATION) { | + | #include <iostream> |
- | ... | + | |
+ | int main() { | ||
+ | for (int i { 1 }; i <= 10; ++i) { | ||
+ | std::cout << i << std::endl; | ||
+ | } | ||
} | } | ||
</code> | </code> | ||
- | Par exemple, pour compter de 1 à 10, on utilise un compteur (variable entière) : | + | affiche : |
- | * initialisation à 1 | + | <code> |
- | * s'arrête lorsque == à 10 | + | 1 |
- | * incrémente à chaque boucle | + | 2 |
+ | 3 | ||
+ | 4 | ||
+ | 5 | ||
+ | 6 | ||
+ | 7 | ||
+ | 8 | ||
+ | 9 | ||
+ | 10 | ||
+ | </code> | ||
- | devient : | + | La syntaxe de la boucle //for// est relativement compacte, et donc il est assez facile de se tromper. Vous devez vous habituer à lire correctement cette syntaxe, en identifiant bien chaque composant de la boucle (initialisation, incrémentation, condition d'arrêt). N'hésitez pas à bien aérer votre code, pour le rendre le plus lisible possible. |
- | <code> | + | |
- | for (int i { 1 }; i <= 10; ++i) { | + | ==== Parcourir une collection ==== |
- | std::cout << i << std::endl; | + | |
+ | Un cas d'utilisation classique de la boucle //for// est de parcourir une collection (lorsque la boucle //range-for// n'est pas utilisable). Pour cela, la méthode la plus générique est d'utiliser les itérateurs. Les trois composants de la boucle peuvent s'écrire : | ||
+ | |||
+ | * initialisation : ''auto it = std::begin(v)'' (ou ''std::cbegin'', ''std::rbegin'', ''std::crbegin'') ; | ||
+ | * incrementation : ''++it'' ; | ||
+ | * condition d'arret : ''it != std::end(v)'' (ou ''std::cend'', ''std::rend'', ''std::crend''). | ||
+ | |||
+ | Notez l'utilisation de la déduction de type avec ''auto'' pour éviter de devoir ecrire le type complet (et long) de l'itérateur. Pour rappel : | ||
+ | |||
+ | <code cpp> | ||
+ | std::vector<int> v; | ||
+ | std::vector<int>::iterator it { std::begin(v) }; | ||
+ | std::vector<int>::const_iterator cit { std::cbegin(v) }; | ||
+ | </code> | ||
+ | |||
+ | Faites bien attention que ''std::begin'' retourne ''iterator'' et ''std::cbegin''' retourne ''const_iterator''. | ||
+ | |||
+ | Le code complet pour parcourir une collection avec des itérateurs est donc le suivant : | ||
+ | |||
+ | <code cpp main.cpp> | ||
+ | #include <iostream> | ||
+ | #include <list> | ||
+ | |||
+ | int main() { | ||
+ | std::list<int> l { 1, 2, 3, 4 }; | ||
+ | for (auto it = std::cbegin(l); it != std::cend(l); ++it) { | ||
+ | std::cout << (*it) << std::endl; | ||
+ | } | ||
} | } | ||
</code> | </code> | ||
+ | |||
+ | affiche : | ||
+ | |||
+ | <code> | ||
+ | 1 | ||
+ | 2 | ||
+ | 3 | ||
+ | 4 | ||
+ | </code> | ||
+ | |||
+ | Notez que la boucle //range-for// fait exactement la même chose en interne. | ||
==== Parcourir un tableau ==== | ==== Parcourir un tableau ==== | ||
- | Avec des iterateurs : | + | Les tableaux sont des collections et peuvent donc être parcouru en utilisant des itérateurs. Mais il est également possible de les parcourir en utilisant l'indice des éléments et l'opérateur d'indexation ''[]''. Dans ce cas, il faut ecrire une boucle //for// basé sur une variable, comme vu précédemment, et l'utiliser pour acceder aux elements. |
- | <code cpp> | + | <code cpp main.cpp> |
- | for (auto it = std::begin(v); it != std::end(v); ++it) { | + | #include <iostream> |
- | std::cout << (*it) << std::endl; | + | #include <vector> |
+ | |||
+ | int main() { | ||
+ | std::vector<char> v { 'a', 'b', 'c', 'd' }; | ||
+ | for (std::size_t i { 0 }; i < v.size(); ++i) { | ||
+ | std::cout << v[i] << std::endl; | ||
+ | } | ||
} | } | ||
</code> | </code> | ||
- | Pars du premier element (begin). A chaque boucle, passe a l'element suivant (++, next aurait pu convenir aussi). S'arrete lorsque arrive a end. | + | affiche : |
- | Condition d'arret = n'est pas executee. Donc quand it == end, la boucle n'est pas executee et (*it) non plus (ca serait invalide). | + | <code> |
+ | a | ||
+ | b | ||
+ | c | ||
+ | d | ||
+ | </code> | ||
- | Pour cela que end(v) ne correspond pas au dernier element d'une collection, mais a l'element "fictif" suivant. | + | <note>**Choix du type pour l'indice** |
- | Avec l'operateur [] : | + | Le type à utiliser pour l'indice devrait être le même type que celui retourné par la fonction ''size()''. En toute rigueur, ce type est ''std::vector<int>::size_type''. |
+ | Cependant, ce type est un peu long à ecrire et équivalent en general a ''std::size_t''. Vous verrez donc souvent ce type dans les codes. | ||
- | ===== while et do while ===== | + | Il est possible d'utiliser la déduction de type, mais il faut faire attention. Si vous écrivez ''auto i = 0'', la littérale ''0'' est de type ''int'' et la variable ''i'' sera donc aussi de type ''int''. Ce qui est une erreur ! (La comparaison d'un type signé et d'un type non signé produit des messages d'avertissement et des comportements parfois inattendus). |
- | Permet de répéter un bloc d'instructions tant qu'une condition est vérifiée. Syntaxe : | + | Il faut donc utiliser la syntaxe avec ''decltype'' : ''decltype(v.size()) i = 0''. Mais cela ne simplifie pas beaucoup la lecture par rapport à l'écriture explicite du type. |
+ | |||
+ | L'utilisation de ''std::size_t'' est un bon compromis entre lisibilité et rigueur. | ||
+ | </note> | ||
+ | |||
+ | |||
+ | ===== Les boucles while et do-while ===== | ||
+ | |||
+ | Les deux dernières syntaxes itératives sont relativement simples à comprendre. Par rapport à la boucle //for//, seule la condition de continuation a une place definie dans ces syntaxes. L'initialisation et l'incrémentation sont placées librement dans le code (voire sont omis). | ||
<code> | <code> | ||
- | while (condition) { | + | while (TEST_CONTINUATION) { |
... | ... | ||
} | } | ||
Ligne 382: | Ligne 467: | ||
do { | do { | ||
... | ... | ||
- | } while (condition); | + | } while (TEST_CONTINUATION); |
</code> | </code> | ||
- | Dans les 2 cas, exécute le bloc tant que la condition est vraie. Différence entre les 2 : avec do while, bloc exécuté au moins 1 fois avant de test la condition ; avec while, commence par tester | + | La difference entre les boucles //while// et //do-while// est que dans la premiere, la condition de continuation est exécutée avant le corps de la boucle. Il est donc possible de sortir tout de suite de la boucle //while// sans entrer dans la boucle. Dans la seconde syntaxe, le corps de la boucle est exécutée avant le test de continuation. La boucle sera donc toujours exécutée au moins une fois. |
+ | |||
+ | En pratique, les boucle //while// et //do-while// sont souvent equivalentes a la boucle //for//, si l'initialisation et l'incrémentation est explicite : | ||
+ | |||
+ | <code> | ||
+ | INITIALISATION; | ||
+ | while (TEST_CONTINUATION) { | ||
+ | ... | ||
+ | INCREMENTATION; | ||
+ | } | ||
+ | </code> | ||