Ricerca…


introduzione

Un'istruzione loop esegue ripetutamente un gruppo di istruzioni finché non viene soddisfatta una condizione. Esistono 3 tipi di loop primitivi in ​​C ++: for, while, and do ... while.

Sintassi

  • while ( condition ) statement ;
  • fare una dichiarazione mentre ( espressione );
  • for ( for-init-statement ; condition ; expression ) statement ;
  • per i (per-gamma-dichiarazione: for-gamma-inizializzatore) istruzione;
  • rompere ;
  • Continua ;

Osservazioni

algorithm chiamate agli algorithm sono generalmente preferibili ai cicli scritti a mano.

Se si desidera qualcosa che un algoritmo già fa (o qualcosa di molto simile), la chiamata all'algoritmo è più chiara, spesso più efficiente e meno soggetta a errori.

Se hai bisogno di un ciclo che faccia qualcosa di abbastanza semplice (ma richiederebbe un groviglio confuso di raccoglitori e adattatori se stai usando un algoritmo), scrivi semplicemente il ciclo.

Range-Based For

C ++ 11

for cicli for possono essere utilizzati per scorrere gli elementi di un intervallo basato su iteratore, senza utilizzare un indice numerico o accedere direttamente agli iteratori:

vector<float> v = {0.4f, 12.5f, 16.234f};

for(auto val: v)
{
    std::cout << val << " ";
}

std::cout << std::endl;

Questo itererà su ogni elemento in v , con val ottiene il valore dell'elemento corrente. La seguente dichiarazione:

for (for-range-declaration : for-range-initializer ) statement

è equivalente a:

{
    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
    }
}

Questa modifica è stata introdotta per il supporto pianificato di Ranges TS in C ++ 20.

In questo caso, il nostro ciclo è equivalente a:

{
    auto&& __range = v;
    auto __begin = v.begin(), __end = v.end();
    for (; __begin != __end; ++__begin) {
        auto val = *__begin;
        std::cout << val << " ";
    }
}

Si noti che auto val dichiara un tipo di valore, che sarà una copia di un valore memorizzato nell'intervallo (lo stiamo inizializzando in copia dall'iteratore mentre procediamo). Se i valori memorizzati nell'intervallo sono costosi da copiare, è possibile utilizzare const auto &val . Inoltre, non è necessario utilizzare l' auto ; è possibile utilizzare un nome di tipo appropriato, purché sia ​​implicitamente convertibile dal tipo di valore dell'intervallo.

Se hai bisogno di accedere all'iteratore, il range-based non può aiutarti (non senza alcuno sforzo, almeno).

Se desideri fare riferimento, puoi farlo:

vector<float> v = {0.4f, 12.5f, 16.234f};

for(float &val: v)
{
    std::cout << val << " ";
}

È possibile eseguire l'iterazione sul riferimento const se si dispone di un contenitore const :

const vector<float> v = {0.4f, 12.5f, 16.234f};

for(const float &val: v)
{
    std::cout << val << " ";
}

Uno userebbe i riferimenti di inoltro quando l'iteratore di sequenza restituisce un oggetto proxy ed è necessario operare su quell'oggetto in modo non const . Nota: molto probabilmente confonderà i lettori del tuo codice.

vector<bool> v(10);

for(auto&& val: v)
{
    val = true;
}

Il tipo "range" fornito alla gamma-base for può essere uno dei seguenti:

  • Matrici di lingue:

    float arr[] = {0.4f, 12.5f, 16.234f};
    
    for(auto val: arr)
    {
        std::cout << val << " ";
    }
    

    Si noti che l'assegnazione di un array dinamico non conta:

    float *arr = new float[3]{0.4f, 12.5f, 16.234f};
    
    for(auto val: arr) //Compile error.
    {
        std::cout << val << " ";
    }
    
  • Qualsiasi tipo che ha funzioni membro begin() e end() , che restituiscono gli iteratori agli elementi del tipo. I contenitori di libreria standard sono idonei, ma è possibile utilizzare anche i tipi definiti dall'utente:

    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 << " ";
        }
    }
    
  • Qualsiasi tipo che ha funzioni di begin(type) e end(type) non membro che possono essere trovate tramite ricerca dipendente dall'argomento, in base al type . Questo è utile per creare un tipo di intervallo senza dover modificare il tipo di classe stesso:

    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 << " ";
        }
    }
    

Per ciclo

Un ciclo for esegue istruzioni nel loop body del loop body , mentre la condition del ciclo è vera. Prima che l' initialization statement del ciclo venga eseguita esattamente una volta. Dopo ogni ciclo, viene eseguita la parte di iteration execution .

Un ciclo for è definito come segue:

for (/*initialization statement*/; /*condition*/; /*iteration execution*/)
{
    // body of the loop
}

Spiegazione delle dichiarazioni del segnaposto:

  • initialization statement : questa istruzione viene eseguita una sola volta, all'inizio del ciclo for . È possibile inserire una dichiarazione di più variabili di un tipo, ad esempio int i = 0, a = 2, b = 3 . Queste variabili sono valide solo nell'ambito del ciclo. Le variabili definite prima del ciclo con lo stesso nome sono nascoste durante l'esecuzione del ciclo.
  • condition : questa istruzione viene valutata prima di ogni esecuzione del corpo del ciclo e interrompe il ciclo se restituisce false .
  • iteration execution : questa istruzione viene eseguita dopo il corpo del ciclo, prima della valutazione della condizione successiva, a meno che il ciclo for venga interrotto nel corpo (per break , goto , return o eccezione generata). È possibile immettere più istruzioni nella parte di iteration execution , ad esempio a++, b+=10, c=b+a .

L'equivalente approssimativo di un ciclo for , riscritto come un ciclo while è:

/*initialization*/
while (/*condition*/)
{
    // body of the loop; using 'continue' will skip to increment part below
    /*iteration execution*/
}

Il caso più comune per l'utilizzo di un ciclo for consiste nell'eseguire le istruzioni un numero specifico di volte. Ad esempio, considera quanto segue:

for(int i = 0; i < 10; i++) {
    std::cout << i << std::endl;
}

Un ciclo valido è anche:

for(int a = 0, b = 10, c = 20; (a+b+c < 100); c--, b++, a+=c) {
    std::cout << a << " " << b << " " << c << std::endl; 
}

Un esempio di nascondere le variabili dichiarate prima di un ciclo è:

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

Ma se vuoi usare la variabile già dichiarata e non nasconderla, ometti la parte della dichiarazione:

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

Gli appunti:

  • Le istruzioni di inizializzazione e di incremento possono eseguire operazioni non correlate all'istruzione di condizione, o nulla del tutto - se si desidera farlo. Tuttavia, per motivi di leggibilità, è consigliabile eseguire solo operazioni direttamente pertinenti al ciclo.
  • Una variabile dichiarata nell'istruzione di inizializzazione è visibile solo all'interno dell'ambito del ciclo for e viene rilasciata al termine del ciclo.
  • Non dimenticare che la variabile che è stata dichiarata nella dichiarazione di initialization statement può essere modificata durante il ciclo, così come la variabile verificata nella condition .

Esempio di un ciclo che conta da 0 a 10:

for (int counter = 0; counter <= 10; ++counter)
{
    std::cout << counter << '\n';
}
// counter is not accessible here (had value 11 at the end)

Spiegazione dei frammenti di codice:

  • int counter = 0 inizializza il counter variabile su 0. (Questa variabile può essere utilizzata solo all'interno del ciclo for .)
  • counter <= 10 è una condizione booleana che controlla se il counter è minore o uguale a 10. Se è true , il ciclo viene eseguito. Se è false , il ciclo termina.
  • ++counter è un'operazione di incremento che incrementa il valore del counter di 1 prima del successivo controllo di condizione.

Lasciando tutte le istruzioni vuote, puoi creare un ciclo infinito:

// infinite loop
for (;;)
    std::cout << "Never ending!\n";

L'equivalente del ciclo while di cui sopra è:

// infinite loop
while (true)
    std::cout << "Never ending!\n";

Tuttavia, un ciclo infinito può ancora essere lasciato usando le istruzioni break , goto o return o lanciando un'eccezione.

Il prossimo esempio comune di iterare su tutti gli elementi di una raccolta STL (es. Un vector ) senza usare l'intestazione <algorithm> è:

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;
}

Mentre loop

Un ciclo while esegue le istruzioni ripetutamente finché la condizione data non diventa false . Questa istruzione di controllo viene utilizzata quando non è noto, in anticipo, quante volte deve essere eseguito un blocco di codice.

Ad esempio, per stampare tutti i numeri da 0 a 9, è possibile utilizzare il seguente codice:

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

Da notare che dal C ++ 17, le prime 2 affermazioni possono essere combinate

while (int i = 0; i < 10)
//... The rest is the same

Per creare un ciclo infinito, è possibile utilizzare il seguente costrutto:

while (true)
{
    // Do something forever (however, you can exit the loop by calling 'break'
}

C'è un'altra variante dei cicli while , ovvero il do...while costrutto. Vedere l' esempio del ciclo di prova per ulteriori informazioni.

Dichiarazione di variabili in condizioni

Nella condizione dei cicli for e while , è anche consentito dichiarare un oggetto. Questo oggetto verrà considerato in ambito fino alla fine del ciclo e verrà mantenuto per ogni iterazione del ciclo:

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.

Tuttavia, non è permesso fare lo stesso con un ciclo do...while ; invece, dichiarare la variabile prima del ciclo e (facoltativamente) racchiudere sia la variabile che il ciclo all'interno di un ambito locale se si desidera che la variabile vada fuori campo dopo la fine del ciclo:

//This doesn't compile
do {
    s = do_something();
} while (short s > 0);

// Good
short s;
do {
    s = do_something();
} while (s > 0);

Questo perché la porzione di istruzione di un ciclo do...while (il corpo del ciclo) viene valutata prima che venga raggiunta la porzione di espressione (il while ) e, quindi, qualsiasi dichiarazione nell'espressione non sarà visibile durante la prima iterazione del ciclo continuo.

Ciclo Do-while

Un ciclo di do-while è molto simile ad un ciclo while , tranne per il fatto che la condizione viene verificata alla fine di ogni ciclo, non all'inizio. Il ciclo è quindi garantito per l'esecuzione almeno una volta.

Il codice seguente stamperà 0 , poiché la condizione verrà valutata su false alla fine della prima iterazione:

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

Nota: non dimenticare il punto e virgola alla fine del while(condition); , che è necessario nel costrutto do-while .

A differenza del ciclo do-while , il seguente non stampa nulla, perché la condizione restituisce false all'inizio della prima iterazione:

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

Nota: è possibile uscire da un ciclo while senza che la condizione diventi falsa utilizzando un'istruzione break , goto o return .

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

Un semplice ciclo do-while viene utilizzato occasionalmente anche per scrivere macro che richiedono il proprio ambito (nel qual caso il punto e virgola finale viene omesso dalla definizione della macro e deve essere fornito dall'utente):

#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);

Istruzioni Loop Control: Break e Continue

Le istruzioni di controllo del ciclo vengono utilizzate per modificare il flusso di esecuzione dalla sua sequenza normale. Quando l'esecuzione lascia un ambito, tutti gli oggetti automatici creati in tale ambito vengono distrutti. Le break e le continue sono istruzioni di controllo del ciclo.

L'istruzione break termina un ciclo senza ulteriori considerazioni.

for (int i = 0; i < 10; i++)
{
    if (i == 4)
        break; // this will immediately exit our loop
    std::cout << i << '\n';
}

Il codice sopra verrà stampato:

1
2
3

L'istruzione continue non esce immediatamente dal ciclo, ma salta il resto del corpo del ciclo e passa all'inizio del ciclo (compreso il controllo della condizione).

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";
}

Il codice sopra verrà stampato:

1 is an odd number
3 is an odd number
5 is an odd number

Poiché tali cambiamenti del flusso di controllo a volte sono difficili da comprendere per gli esseri umani, l' break e il continue vengono usati con parsimonia. L'implementazione più semplice di solito è più facile da leggere e capire. Ad esempio, il primo ciclo for con l' break sopra potrebbe essere riscritto come:

for (int i = 0; i < 4; i++)
{
    std::cout << i << '\n';
}

Il secondo esempio con continue potrebbe essere riscritto come:

for (int i = 0; i < 6; i++)
{
    if (i % 2 != 0) {
        std::cout << i << " is an odd number\n";
    }
}

Intervallo: per un sottogruppo

Utilizzando i loop di base-intervallo, è possibile eseguire il loop su una sotto-parte di un determinato contenitore o altro intervallo generando un oggetto proxy che si qualifica per loop basati su intervalli.

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();
}

ora possiamo fare:

std::vector<int> v = {1,2,3,4};

for (auto i : except_first(v))
  std::cout << i << '\n';

e stampare

2
3
4

Tenere presente che gli oggetti intermedi generati nella parte for(:range_expression) del ciclo for saranno scaduti dal momento for inizia il ciclo for .



Modified text is an extract of the original Stack Overflow Documentation
Autorizzato sotto CC BY-SA 3.0
Non affiliato con Stack Overflow