Zoeken…


Invoering

Een lusinstructie voert een groep instructies herhaaldelijk uit totdat aan een voorwaarde is voldaan. Er zijn 3 soorten primitieve lussen in C ++: voor, while en do ... while.

Syntaxis

  • terwijl ( voorwaarde ) verklaring ;
  • do statement while ( expressie );
  • voor ( voor-init-verklaring ; voorwaarde ; uitdrukking ) verklaring ;
  • voor ( voor-bereik-aangifte : voor-bereik-initialisatie ) verklaring ;
  • breken;
  • doorgaan met ;

Opmerkingen

algorithm hebben over het algemeen de voorkeur boven handgeschreven lussen.

Als u iets wilt dat een algoritme al doet (of iets dat erg op elkaar lijkt), is de algoritmeaanroep duidelijker, vaak efficiënter en minder foutgevoelig.

Als je een lus nodig hebt die redelijk eenvoudig is (maar als je een algoritme zou gebruiken, een verwarrende hoeveelheid bindmiddelen en adapters nodig zou hebben), schrijf dan gewoon de lus.

Bereikgebaseerd voor

C ++ 11

for lussen kunnen worden gebruikt om te itereren over de elementen van een iterator-gebaseerd bereik, zonder een numerieke index te gebruiken of rechtstreeks toegang te krijgen tot de iterators:

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

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

std::cout << std::endl;

Dit zal elk element in v herhalen, waarbij val de waarde van het huidige element krijgt. De volgende verklaring:

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

is gelijk aan:

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

Deze wijziging is geïntroduceerd voor de geplande ondersteuning van Ranges TS in C ++ 20.

In dit geval is onze lus gelijk aan:

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

Merk op dat auto val een waardetype declareert, wat een kopie zal zijn van een waarde die in het bereik is opgeslagen (we initialiseren het door de iterator terwijl we verder gaan). Als de waarden die in het bereik zijn opgeslagen, duur zijn om te kopiëren, kunt u const auto &val . U bent ook niet verplicht om auto te gebruiken; u kunt een geschikte typenaam gebruiken, zolang deze impliciet converteerbaar is van het waardetype van het bereik.

Als u toegang tot de iterator nodig hebt, kan bereikgebaseerd voor u niet helpen (niet zonder enige inspanning, tenminste).

Als u ernaar wilt verwijzen, kunt u dit doen:

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

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

Je zou kunnen doorgaan op const referentie als je const container hebt:

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

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

Men zou doorstuurreferenties gebruiken wanneer de sequentie-iterator een proxy-object retourneert en u op dat object op een niet- const manier moet werken. Opmerking: het zal hoogstwaarschijnlijk lezers van uw code verwarren.

vector<bool> v(10);

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

Het type "bereik" dat op bereik is gebaseerd for kan een van de volgende zijn:

  • Taalarrays:

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

    Merk op dat het toewijzen van een dynamische array niet telt:

    float *arr = new float[3]{0.4f, 12.5f, 16.234f};
    
    for(auto val: arr) //Compile error.
    {
        std::cout << val << " ";
    }
    
  • Elk type met lidfuncties begin() en end() , die iterators retourneren naar de elementen van het type. De standaardbibliotheekcontainers komen in aanmerking, maar door de gebruiker gedefinieerde typen kunnen ook worden gebruikt:

    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 << " ";
        }
    }
    
  • Elk type met niet-lid begin(type) en end(type) functies die kunnen worden gevonden via argumentafhankelijke opzoeking, op basis van type . Dit is handig voor het maken van een bereiktype zonder het klasse-type zelf te moeten wijzigen:

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

For loop

Een for -lus voert instructies in het loop body , terwijl de lus condition waar is. Voordat de lus initialization statement precies één keer wordt uitgevoerd. Na elke cyclus wordt het iteration execution uitgevoerd.

Een for lus wordt als volgt gedefinieerd:

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

Verklaring van de verklaringen van tijdelijke aanduidingen:

  • initialization statement : deze instructie wordt slechts eenmaal uitgevoerd, aan het begin van de for lus. U kunt een verklaring van meerdere variabelen van één type invoeren, zoals int i = 0, a = 2, b = 3 . Deze variabelen zijn alleen geldig binnen het bereik van de lus. Variabelen die vóór de lus met dezelfde naam zijn gedefinieerd, worden tijdens de uitvoering van de lus verborgen.
  • condition : Deze verklaring wordt voorafgaand aan elke lus uitvoering lichaam geëvalueerd, en breekt de lus als het wordt geëvalueerd als false .
  • iteration execution : deze instructie wordt uitgevoerd na de body van de lus, voorafgaand aan de volgende evaluatie van de conditie , tenzij de for lus in de body wordt afgebroken (door break , goto , return of een uitzondering die wordt gegenereerd). U kunt meerdere instructies invoeren in het iteration execution , zoals a++, b+=10, c=b+a .

Het ruwe equivalent van een for lus, herschreven als een while lus is:

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

Het meest voorkomende geval voor het gebruik van een for lus is om een bepaald aantal keren instructies uit te voeren. Overweeg bijvoorbeeld het volgende:

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

Een geldige lus is ook:

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

Een voorbeeld van het verbergen van gedeclareerde variabelen voor een lus is:

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

Maar als u de reeds gedeclareerde variabele wilt gebruiken en deze niet wilt verbergen, laat u het declaratiegedeelte weg:

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

Opmerkingen:

  • De initialisatie- en incrementinstructies kunnen bewerkingen uitvoeren die geen verband houden met de conditieverklaring of helemaal niets - als u dat wilt. Om redenen van leesbaarheid is het echter best om alleen bewerkingen uit te voeren die direct relevant zijn voor de lus.
  • Een variabele gedeclareerd in de initialisatie-instructie is alleen zichtbaar binnen het bereik van de for lus en wordt vrijgegeven bij beëindiging van de lus.
  • Vergeet niet dat de variabele die in de initialization statement is gedeclareerd tijdens de lus kan worden gewijzigd, evenals de variabele die in de condition aangevinkt.

Voorbeeld van een lus die van 0 tot 10 telt:

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

Verklaring van de codefragmenten:

  • int counter = 0 initialiseert de variabele counter op 0. (Deze variabele kan alleen binnen de for lus worden gebruikt.)
  • counter <= 10 is een Booleaanse voorwaarde die controleert of counter kleiner is dan of gelijk aan 10. Als het true , wordt de lus uitgevoerd. Als het false , eindigt de lus.
  • ++counter is een incrementele bewerking die de waarde van counter met 1 verhoogt vóór de volgende voorwaardecontrole.

Door alle instructies leeg te laten, kunt u een oneindige lus maken:

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

Het while lus-equivalent van het bovenstaande is:

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

Een oneindige lus kan echter nog steeds worden verlaten door de instructies break , goto of return of door een uitzondering te maken.

Het volgende veel voorkomende voorbeeld van het herhalen van alle elementen uit een STL-verzameling (bijvoorbeeld een vector ) zonder de kop <algorithm> is:

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

Herhalingslus

Een while lus voert herhaaldelijk instructies uit totdat de gegeven voorwaarde als false evalueert. Deze besturingsopdracht wordt gebruikt wanneer vooraf niet bekend is hoe vaak een codeblok moet worden uitgevoerd.

Om bijvoorbeeld alle nummers van 0 tot 9 af te drukken, kan de volgende code worden gebruikt:

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

Merk op dat sinds C ++ 17 de eerste 2 statements kunnen worden gecombineerd

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

Om een oneindige lus te maken, kan het volgende construct worden gebruikt:

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

Er is nog een variant van while lussen, namelijk de do...while constructie. Zie het do-while-lusvoorbeeld voor meer informatie.

Verklaring van variabelen in omstandigheden

In de staat van de for en while lussen is het ook toegestaan om een object te declareren. Dit object wordt tot het einde van de lus beschouwd en blijft door elke iteratie van de lus bestaan:

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.

Het is echter niet toegestaan om hetzelfde te doen met een do...while lus; declareer in plaats daarvan de variabele vóór de lus en sluit (optioneel) zowel de variabele als de lus in een lokaal bereik in als u wilt dat de variabele buiten het bereik valt nadat de lus is geëindigd:

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

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

Dit komt omdat het instructiegedeelte van een do...while lus (het lichaam van de lus) wordt geëvalueerd voordat het expressiegedeelte (de while ) wordt bereikt, en daarom is elke verklaring in de expressie niet zichtbaar tijdens de eerste iteratie van de lus.

Do-while-lus

Een do-while- lus lijkt erg op een while- lus, behalve dat de toestand aan het einde van elke cyclus wordt gecontroleerd, niet aan het begin. De lus wordt daarom gegarandeerd minstens één keer uitgevoerd.

De volgende code drukt 0 , omdat de voorwaarde aan het einde van de eerste iteratie als false wordt geëvalueerd:

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

Opmerking: vergeet de puntkomma niet aan het einde van de while(condition); , wat nodig is in het do-while- construct.

In tegenstelling tot de do-while- lus, zal het volgende niets afdrukken, omdat de voorwaarde bij het begin van de eerste iteratie als false evalueert:

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

Opmerking: een while- lus kan worden verlaten zonder dat de voorwaarde onwaar wordt door een instructie break , goto of return te gebruiken.

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

Een triviale do-while- lus wordt ook af en toe gebruikt om macro's te schrijven die hun eigen bereik vereisen (in welk geval de puntkomma achterwege wordt gelaten uit de macrodefinitie en moet worden verstrekt door de gebruiker):

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

Loop Control-instructies: Break and Continue

Lusbesturingsinstructies worden gebruikt om de uitvoering van de normale volgorde te wijzigen. Wanneer de uitvoering een bereik verlaat, worden alle automatische objecten die in dat bereik zijn gemaakt, vernietigd. De break en continue zijn loop control statements.

De break instructie beëindigt een lus zonder verdere overweging.

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

De bovenstaande code wordt afgedrukt:

1
2
3

De instructie continue verlaat de lus niet onmiddellijk, maar slaat de rest van de lus over en gaat naar de bovenkant van de lus (inclusief het controleren van de voorwaarde).

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

De bovenstaande code wordt afgedrukt:

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

Omdat dergelijke controlestroomveranderingen voor mensen soms moeilijk te begrijpen, te break en door te continue worden spaarzaam gebruikt. Eenvoudigere implementatie is meestal gemakkelijker te lezen en te begrijpen. De eerste for lus met de bovenstaande break kan bijvoorbeeld worden herschreven als:

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

Het tweede voorbeeld met continue kan worden herschreven als:

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

Bereik - over een subbereik

Met behulp van bereik-basislussen kunt u een lus maken over een subdeel van een bepaalde container of een ander bereik door een proxy-object te genereren dat in aanmerking komt voor op bereik gebaseerde lussen.

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

nu kunnen we doen:

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

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

en afdrukken

2
3
4

Houd er rekening mee dat for(:range_expression) die zijn gegenereerd in het for(:range_expression) gedeelte van de for lus zijn verlopen tegen de tijd dat de for lus start.



Modified text is an extract of the original Stack Overflow Documentation
Licentie onder CC BY-SA 3.0
Niet aangesloten bij Stack Overflow