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
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
}
}
{
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()
enend()
, 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)
enend(type)
functies die kunnen worden gevonden via argumentafhankelijke opzoeking, op basis vantype
. 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 defor
lus. U kunt een verklaring van meerdere variabelen van één type invoeren, zoalsint 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 alsfalse
. -
iteration execution
: deze instructie wordt uitgevoerd na de body van de lus, voorafgaand aan de volgende evaluatie van de conditie , tenzij defor
lus in de body wordt afgebroken (doorbreak
,goto
,return
of een uitzondering die wordt gegenereerd). U kunt meerdere instructies invoeren in hetiteration execution
, zoalsa++, 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 decondition
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 variabelecounter
op 0. (Deze variabele kan alleen binnen defor
lus worden gebruikt.) -
counter <= 10
is een Booleaanse voorwaarde die controleert ofcounter
kleiner is dan of gelijk aan 10. Als hettrue
, wordt de lus uitgevoerd. Als hetfalse
, eindigt de lus. -
++counter
is een incrementele bewerking die de waarde vancounter
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
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.