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

C ++ 11

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
    }
}
C ++ 17
{
    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() et end() , 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) et end(type) qui peuvent être trouvées via la recherche dépendante des arguments, en fonction du type . 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 boucle for . Vous pouvez saisir une déclaration de plusieurs variables d'un type, telles que int 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 boucle for soit abandonnée dans le corps (par break , goto , return ou une exception étant lancée). Vous pouvez entrer plusieurs instructions dans la partie d' iteration execution l' iteration execution , telles que a++, 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 la condition .

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 le counter variable à 0. (Cette variable ne peut être utilisée qu'à l'intérieur de la boucle for .)
  • counter <= 10 est une condition booléenne qui vérifie si le counter est inférieur ou égal à 10. Si c'est true , la boucle s'exécute. Si c'est false , la boucle se termine.
  • ++counter est une opération d'incrémentation qui incrémente la valeur du counter 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
C ++ 17

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.



Modified text is an extract of the original Stack Overflow Documentation
Sous licence CC BY-SA 3.0
Non affilié à Stack Overflow