Recherche…
Introduction
Une instruction de boucle exécute un groupe d'instructions à plusieurs reprises jusqu'à ce qu'une condition soit remplie. Il existe 3 types de boucles primitives en C ++: for, while et do ... while.
Syntaxe
- while déclaration ( condition );
- faire une déclaration tandis que ( expression );
- (pour la -instruction-initialisation, condition; expression) déclaration;
- pour l' instruction ( for-range-declaration : for-range-initializer );
- Pause ;
- continuer ;
Remarques
algorithm
appels d' algorithm
sont généralement préférables aux boucles écrites à la main.
Si vous voulez quelque chose qu'un algorithme fait déjà (ou quelque chose de très similaire), l'appel d'algorithme est plus clair, souvent plus efficace et moins sujet aux erreurs.
Si vous avez besoin d'une boucle qui fait quelque chose d'assez simple (mais nécessiterait un enchevêtrement de liants et d'adaptateurs si vous utilisiez un algorithme), alors écrivez simplement la boucle.
Basé sur la gamme pour
for
loops peut être utilisé pour parcourir les éléments d'une plage basée sur un itérateur, sans utiliser d'index numérique ou accéder directement aux itérateurs:
vector<float> v = {0.4f, 12.5f, 16.234f};
for(auto val: v)
{
std::cout << val << " ";
}
std::cout << std::endl;
Cela va parcourir tous les éléments de v
, avec val
la valeur de l'élément en cours. La déclaration suivante:
for (for-range-declaration : for-range-initializer ) statement
est équivalent à:
{
auto&& __range = for-range-initializer;
auto __begin = begin-expr, __end = end-expr;
for (; __begin != __end; ++__begin) {
for-range-declaration = *__begin;
statement
}
}
{
auto&& __range = for-range-initializer;
auto __begin = begin-expr;
auto __end = end-expr; // end is allowed to be a different type than begin in C++17
for (; __begin != __end; ++__begin) {
for-range-declaration = *__begin;
statement
}
}
Cette modification a été introduite pour la prise en charge prévue de Ranges TS en C ++ 20.
Dans ce cas, notre boucle est équivalente à:
{
auto&& __range = v;
auto __begin = v.begin(), __end = v.end();
for (; __begin != __end; ++__begin) {
auto val = *__begin;
std::cout << val << " ";
}
}
Notez que auto val
déclare un type de valeur, qui sera une copie d'une valeur stockée dans la plage (nous l'initialisons depuis l'itérateur). Si les valeurs stockées dans la plage sont coûteuses à copier, vous pouvez utiliser const auto &val
. Vous n'êtes pas non plus obligé d'utiliser auto
; Vous pouvez utiliser un nom de fichier approprié, à condition qu'il soit implicitement convertible à partir du type de valeur de la plage.
Si vous avez besoin d’accéder à l’itérateur, le mode basé sur les plages ne peut pas vous aider (au moins pas sans effort).
Si vous souhaitez le référencer, vous pouvez le faire:
vector<float> v = {0.4f, 12.5f, 16.234f};
for(float &val: v)
{
std::cout << val << " ";
}
Vous pouvez itérer sur la référence const
si vous avez un conteneur const
:
const vector<float> v = {0.4f, 12.5f, 16.234f};
for(const float &val: v)
{
std::cout << val << " ";
}
On utilisera des références de transfert lorsque l'itérateur de séquence retourne un objet proxy et que vous devez opérer sur cet objet d'une manière non const
. Remarque: il est fort probable que cela va dérouter les lecteurs de votre code.
vector<bool> v(10);
for(auto&& val: v)
{
val = true;
}
Le type « plage » fourni à la gamme-base for
peut être l' un des éléments suivants:
Tableaux de langues:
float arr[] = {0.4f, 12.5f, 16.234f}; for(auto val: arr) { std::cout << val << " "; }
Notez que l'allocation d'un tableau dynamique ne compte pas:
float *arr = new float[3]{0.4f, 12.5f, 16.234f}; for(auto val: arr) //Compile error. { std::cout << val << " "; }
Tout type ayant des fonctions membres
begin()
etend()
, qui renvoient des itérateurs aux éléments du type. Les conteneurs de bibliothèque standard se qualifient, mais les types définis par l'utilisateur peuvent également être utilisés:struct Rng { float arr[3]; // pointers are iterators const float* begin() const {return &arr[0];} const float* end() const {return &arr[3];} float* begin() {return &arr[0];} float* end() {return &arr[3];} }; int main() { Rng rng = {{0.4f, 12.5f, 16.234f}}; for(auto val: rng) { std::cout << val << " "; } }
Tout type qui a des fonctions
begin(type)
etend(type)
qui peuvent être trouvées via la recherche dépendante des arguments, en fonction dutype
. Ceci est utile pour créer un type de plage sans avoir à modifier le type de classe lui-même:namespace Mine { struct Rng {float arr[3];}; // pointers are iterators const float* begin(const Rng &rng) {return &rng.arr[0];} const float* end(const Rng &rng) {return &rng.arr[3];} float* begin(Rng &rng) {return &rng.arr[0];} float* end(Rng &rng) {return &rng.arr[3];} } int main() { Mine::Rng rng = {{0.4f, 12.5f, 16.234f}}; for(auto val: rng) { std::cout << val << " "; } }
Pour la boucle
Une boucle for
exécute des instructions dans le loop body
la loop body
, tandis que la condition
la boucle est vraie. Avant que l' initialization statement
la boucle ne soit exécutée exactement une fois. Après chaque cycle, la partie d' iteration execution
l' iteration execution
est exécutée.
Une boucle for
est définie comme suit:
for (/*initialization statement*/; /*condition*/; /*iteration execution*/)
{
// body of the loop
}
Explication des énoncés de placeholder:
-
initialization statement
: cette instruction n'est exécutée qu'une seule fois, au début de la bouclefor
. Vous pouvez saisir une déclaration de plusieurs variables d'un type, telles queint i = 0, a = 2, b = 3
. Ces variables ne sont valides que dans le cadre de la boucle. Les variables définies avant la boucle du même nom sont masquées lors de l'exécution de la boucle. -
condition
: cette instruction est évaluée avant chaque exécution de corps de boucle et annule la boucle si elle est évaluée àfalse
. -
iteration execution
: cette instruction est exécutée après le corps de la boucle, avant l'évaluation de la condition suivante, à moins que la bouclefor
soit abandonnée dans le corps (parbreak
,goto
,return
ou une exception étant lancée). Vous pouvez entrer plusieurs instructions dans la partie d'iteration execution
l'iteration execution
, telles quea++, b+=10, c=b+a
.
L'équivalent approximatif d'une boucle for
, réécrit en tant while
boucle while est:
/*initialization*/
while (/*condition*/)
{
// body of the loop; using 'continue' will skip to increment part below
/*iteration execution*/
}
Le cas le plus courant d'utilisation d'une boucle for
consiste à exécuter des instructions un nombre de fois spécifique. Par exemple, prenez en compte les éléments suivants:
for(int i = 0; i < 10; i++) {
std::cout << i << std::endl;
}
Une boucle valide est également:
for(int a = 0, b = 10, c = 20; (a+b+c < 100); c--, b++, a+=c) {
std::cout << a << " " << b << " " << c << std::endl;
}
Un exemple de masquage de variables déclarées avant une boucle est:
int i = 99; //i = 99
for(int i = 0; i < 10; i++) { //we declare a new variable i
//some operations, the value of i ranges from 0 to 9 during loop execution
}
//after the loop is executed, we can access i with value of 99
Mais si vous souhaitez utiliser la variable déjà déclarée et ne pas la masquer, omettez la partie déclaration:
int i = 99; //i = 99
for(i = 0; i < 10; i++) { //we are using already declared variable i
//some operations, the value of i ranges from 0 to 9 during loop execution
}
//after the loop is executed, we can access i with value of 10
Remarques:
- Les instructions d'initialisation et d'incrément peuvent effectuer des opérations sans rapport avec la déclaration de condition, ou rien du tout, si vous le souhaitez. Mais pour des raisons de lisibilité, il est recommandé de ne réaliser que des opérations directement liées à la boucle.
- Une variable déclarée dans l'instruction d'initialisation est visible uniquement dans la portée de la boucle
for
et est libérée à la fin de la boucle. - N'oubliez pas que la variable déclarée dans l'
initialization statement
peut être modifiée pendant la boucle, ainsi que la variable vérifiée dans lacondition
.
Exemple de boucle qui compte de 0 à 10:
for (int counter = 0; counter <= 10; ++counter)
{
std::cout << counter << '\n';
}
// counter is not accessible here (had value 11 at the end)
Explication des fragments de code:
-
int counter = 0
initialise lecounter
variable à 0. (Cette variable ne peut être utilisée qu'à l'intérieur de la bouclefor
.) -
counter <= 10
est une condition booléenne qui vérifie si lecounter
est inférieur ou égal à 10. Si c'esttrue
, la boucle s'exécute. Si c'estfalse
, la boucle se termine. -
++counter
est une opération d'incrémentation qui incrémente la valeur ducounter
de 1 avant la vérification de condition suivante.
En laissant toutes les instructions vides, vous pouvez créer une boucle infinie:
// infinite loop
for (;;)
std::cout << "Never ending!\n";
Le while
en boucle équivalente de ce qui précède est la suivante :
// infinite loop
while (true)
std::cout << "Never ending!\n";
Cependant, une boucle infinie peut toujours être laissée en utilisant les instructions break
, goto
ou return
ou en lançant une exception.
Le prochain exemple courant d'itération sur tous les éléments d'une collection STL (par exemple, un vector
) sans utiliser l' <algorithm>
tête <algorithm>
est:
std::vector<std::string> names = {"Albert Einstein", "Stephen Hawking", "Michael Ellis"};
for(std::vector<std::string>::iterator it = names.begin(); it != names.end(); ++it) {
std::cout << *it << std::endl;
}
En boucle
A while
boucle exécute des instructions à plusieurs reprises jusqu'à ce que la condition donnée est évaluée à false
. Cette instruction de contrôle est utilisée quand on ne sait pas à l'avance combien de fois un bloc de code doit être exécuté.
Par exemple, pour imprimer tous les nombres de 0 à 9, le code suivant peut être utilisé:
int i = 0;
while (i < 10)
{
std::cout << i << " ";
++i; // Increment counter
}
std::cout << std::endl; // End of line; "0 1 2 3 4 5 6 7 8 9" is printed to the console
Notez que depuis C ++ 17, les 2 premières instructions peuvent être combinées
while (int i = 0; i < 10)
//... The rest is the same
Pour créer une boucle infinie, la construction suivante peut être utilisée:
while (true)
{
// Do something forever (however, you can exit the loop by calling 'break'
}
Il existe une autre variante des boucles while
, à savoir la construction do...while
. Voir l' exemple de boucle do-while pour plus d'informations.
Déclaration de variables dans des conditions
Dans la condition des boucles for
et while
, il est également permis de déclarer un objet. Cet objet sera considéré comme ayant une portée jusqu'à la fin de la boucle et persistera à chaque itération de la boucle:
for (int i = 0; i < 5; ++i) {
do_something(i);
}
// i is no longer in scope.
for (auto& a : some_container) {
a.do_something();
}
// a is no longer in scope.
while(std::shared_ptr<Object> p = get_object()) {
p->do_something();
}
// p is no longer in scope.
Cependant, il n'est pas permis de faire la même chose avec une boucle do...while
while; au lieu de cela, déclarez la variable avant la boucle et (éventuellement) incluez la variable et la boucle dans une étendue locale si vous voulez que la variable sorte de la portée après la fin de la boucle:
//This doesn't compile
do {
s = do_something();
} while (short s > 0);
// Good
short s;
do {
s = do_something();
} while (s > 0);
En effet , la partie de la déclaration d'une do...while
la boucle (le corps de la boucle) est évaluée avant la partie d'expression (le while
) est atteinte, et donc, toute déclaration dans l'expression ne sera pas visible lors de la première itération de la boucle.
Boucle Do-while
Une boucle do-while est très similaire à une boucle while, sauf que la condition est vérifiée à la fin de chaque cycle, pas au début. La boucle est donc garantie pour s'exécuter au moins une fois.
Le code suivant affichera 0
, comme condition évaluera à false
à la fin de la première itération:
int i =0;
do
{
std::cout << i;
++i; // Increment counter
}
while (i < 0);
std::cout << std::endl; // End of line; 0 is printed to the console
Note: N'oubliez pas le point-virgule à la fin de while(condition);
, qui est nécessaire dans la construction do-while .
Contrairement à la boucle do- while, les éléments suivants n'imprimeront rien, car la condition est false
au début de la première itération:
int i =0;
while (i < 0)
{
std::cout << i;
++i; // Increment counter
}
std::cout << std::endl; // End of line; nothing is printed to the console
Remarque: Une boucle while peut être sorti sans la condition de devenir fausse en utilisant une break
, goto
ou return
déclaration.
int i = 0;
do
{
std::cout << i;
++i; // Increment counter
if (i > 5)
{
break;
}
}
while (true);
std::cout << std::endl; // End of line; 0 1 2 3 4 5 is printed to the console
Une boucle do-while trivial est aussi parfois utilisé pour écrire des macros qui nécessitent leur propre champ (dans ce cas , le point - virgule final est omis de la définition de la macro et doit être fourni par l'utilisateur):
#define BAD_MACRO(x) f1(x); f2(x); f3(x);
// Only the call to f1 is protected by the condition here
if (cond) BAD_MACRO(var);
#define GOOD_MACRO(x) do { f1(x); f2(x); f3(x); } while(0)
// All calls are protected here
if (cond) GOOD_MACRO(var);
Instructions de contrôle de boucle: Break and Continue
Les instructions de contrôle de boucle sont utilisées pour modifier le flux d'exécution à partir de sa séquence normale. Lorsque l'exécution laisse une étendue, tous les objets automatiques créés dans cette étendue sont détruits. Le break
and continue
sont des instructions de contrôle de boucle.
L'instruction break
termine une boucle sans autre considération.
for (int i = 0; i < 10; i++)
{
if (i == 4)
break; // this will immediately exit our loop
std::cout << i << '\n';
}
Le code ci-dessus sera imprimé:
1
2
3
L'instruction continue
ne quitte pas immédiatement la boucle, mais ignore le reste du corps de la boucle et se dirige vers le haut de la boucle (y compris la vérification de la condition).
for (int i = 0; i < 6; i++)
{
if (i % 2 == 0) // evaluates to true if i is even
continue; // this will immediately go back to the start of the loop
/* the next line will only be reached if the above "continue" statement
does not execute */
std::cout << i << " is an odd number\n";
}
Le code ci-dessus sera imprimé:
1 is an odd number
3 is an odd number
5 is an odd number
En raison de ces changements de flux de contrôle sont parfois difficiles pour l' homme de comprendre facilement, break
et continue
sont utilisés avec parcimonie. Une implémentation plus simple est généralement plus facile à lire et à comprendre. Par exemple, la première for
la boucle avec la break
ci - dessus peut être réécrite comme:
for (int i = 0; i < 4; i++)
{
std::cout << i << '\n';
}
Le deuxième exemple avec continue
pourrait être réécrit comme continue
:
for (int i = 0; i < 6; i++)
{
if (i % 2 != 0) {
std::cout << i << " is an odd number\n";
}
}
Portée pour une sous-gamme
En utilisant des boucles de base, vous pouvez effectuer une boucle sur une sous-partie d'un conteneur donné ou d'une autre plage en générant un objet proxy qualifié pour les boucles basées sur la plage.
template<class Iterator, class Sentinel=Iterator>
struct range_t {
Iterator b;
Sentinel e;
Iterator begin() const { return b; }
Sentinel end() const { return e; }
bool empty() const { return begin()==end(); }
range_t without_front( std::size_t count=1 ) const {
if (std::is_same< std::random_access_iterator_tag, typename std::iterator_traits<Iterator>::iterator_category >{} ) {
count = (std::min)(std::size_t(std::distance(b,e)), count);
}
return {std::next(b, count), e};
}
range_t without_back( std::size_t count=1 ) const {
if (std::is_same< std::random_access_iterator_tag, typename std::iterator_traits<Iterator>::iterator_category >{} ) {
count = (std::min)(std::size_t(std::distance(b,e)), count);
}
return {b, std::prev(e, count)};
}
};
template<class Iterator, class Sentinel>
range_t<Iterator, Sentinel> range( Iterator b, Sentinal e ) {
return {b,e};
}
template<class Iterable>
auto range( Iterable& r ) {
using std::begin; using std::end;
return range(begin(r),end(r));
}
template<class C>
auto except_first( C& c ) {
auto r = range(c);
if (r.empty()) return r;
return r.without_front();
}
maintenant nous pouvons faire:
std::vector<int> v = {1,2,3,4};
for (auto i : except_first(v))
std::cout << i << '\n';
et imprimer
2
3
4
Sachez que les objets intermédiaires générés dans la partie for(:range_expression)
de la boucle for
auront expiré au moment où la boucle for
démarre.