Sök…
Introduktion
Ett loop-uttalande kör en grupp uttalanden upprepade gånger tills ett villkor är uppfyllt. Det finns 3 typer av primitiva öglor i C ++: för, medan och gör ... medan.
Syntax
- medan ( villkor ) uttalande ;
- göra uttalande medan ( uttryck );
- för ( för-init-uttalande ; villkor ; uttryck ) uttalande ;
- för ( för-intervall-deklaration : för-intervall-initialisering ) uttalande ;
- ha sönder ;
- Fortsätta ;
Anmärkningar
algorithm
är generellt att föredra framför handskrivna slingor.
Om du vill ha något som en algoritm redan gör (eller något mycket liknande) är algoritmsamtalet tydligare, ofta mer effektivt och mindre fel benägna.
Om du behöver en slinga som gör något ganska enkelt (men som kräver en förvirrande fläns av bindemedel och adaptrar om du använder en algoritm), skriv bara slingan.
Range-baserad för
for
slingor kan användas för att iterera över elementen i ett iteratorbaserat intervall, utan att använda ett numeriskt index eller direkt komma åt iteratorerna:
vector<float> v = {0.4f, 12.5f, 16.234f};
for(auto val: v)
{
std::cout << val << " ";
}
std::cout << std::endl;
Detta upprepas över varje element i v
, med val
får värdet på det aktuella elementet. Följande uttalande:
for (for-range-declaration : for-range-initializer ) statement
är ekvivalent med:
{
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
}
}
Denna förändring infördes för det planerade stödet för Ranges TS i C ++ 20.
I detta fall motsvarar vår slinga:
{
auto&& __range = v;
auto __begin = v.begin(), __end = v.end();
for (; __begin != __end; ++__begin) {
auto val = *__begin;
std::cout << val << " ";
}
}
Observera att auto val
deklarerar en värdetyp, som kommer att vara en kopia av ett värde lagrat i intervallet (vi kopierar initiera det från iteratorn när vi går). Om värdena som lagras i intervallet är dyra att kopiera, kanske du vill använda const auto &val
. Du behöver inte heller använda auto
; Du kan använda ett lämpligt typnamn, så länge det implicit är konvertibelt från områdets värdetyp.
Om du behöver åtkomst till iteratorn, kan inte-baserad för hjälpa dig (inte utan någon ansträngning, åtminstone).
Om du vill hänvisa till det kan du göra det:
vector<float> v = {0.4f, 12.5f, 16.234f};
for(float &val: v)
{
std::cout << val << " ";
}
Du kan iterera på const
referens om du har const
container:
const vector<float> v = {0.4f, 12.5f, 16.234f};
for(const float &val: v)
{
std::cout << val << " ";
}
Man skulle använda vidarebefordringsreferenser när sekvens iteratorn returnerar ett proxyobjekt och du måste använda det objektet på ett icke- const
sätt. Obs! Det kommer troligen att förvirra läsarna av din kod.
vector<bool> v(10);
for(auto&& val: v)
{
val = true;
}
Typen "intervall" som tillhandahålls för intervallbaserad for
kan vara en av följande:
Språkuppsättningar:
float arr[] = {0.4f, 12.5f, 16.234f}; for(auto val: arr) { std::cout << val << " "; }
Observera att tilldelning av en dynamisk grupp inte räknas:
float *arr = new float[3]{0.4f, 12.5f, 16.234f}; for(auto val: arr) //Compile error. { std::cout << val << " "; }
Alla typer som har medlemsfunktioner
begin()
ochend()
, som returnerar iteratorer till elementen i typen. Standardbibliotekets behållare är kvalificerade, men användardefinierade typer kan också användas: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 << " "; } }
Varje typ som har icke-medlemmar
begin(type)
ochend(type)
funktioner som kan hittas via argumentberoende uppslag, baserat påtype
. Detta är användbart för att skapa en intervalltyp utan att behöva ändra själva klasstypen: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 << " "; } }
För slinga
A for
loop exekverar uttalanden i loop body
, medan slingan condition
är sant. Innan initialization statement
exekveras en gång. Efter varje cykel körs iteration execution
.
En for
slinga definieras enligt följande:
for (/*initialization statement*/; /*condition*/; /*iteration execution*/)
{
// body of the loop
}
Förklaring av uttalanden om platshållare:
-
initialization statement
: Detta uttalande körs endast en gång i början avfor
loopen. Du kan ange en deklaration av flera variabler av en typ, till exempelint i = 0, a = 2, b = 3
. Dessa variabler är endast giltiga inom loopens omfattning. Variabler definierade före slingan med samma namn döljs under körningen av slingan. -
condition
: Detta uttalande får utvärderas inför varje exekverings slingan och avbryter loopen om det beräknas tillfalse
. -
iteration execution
: Detta påstående får exekveras efter slingan, inför nästa tillstånd utvärdering, såvida inte denfor
slingan avbryts i kroppen (genombreak
,goto
,return
eller ett undantag som kastas). Du kan ange flera uttalanden iiteration execution
, till exempela++, b+=10, c=b+a
.
Den grova ekvivalent av en for
slinga, skrivas om som en while
slinga är:
/*initialization*/
while (/*condition*/)
{
// body of the loop; using 'continue' will skip to increment part below
/*iteration execution*/
}
Det vanligaste fallet för att använda en for
loop är att köra uttalanden ett visst antal gånger. Tänk till exempel på följande:
for(int i = 0; i < 10; i++) {
std::cout << i << std::endl;
}
En giltig slinga är också:
for(int a = 0, b = 10, c = 20; (a+b+c < 100); c--, b++, a+=c) {
std::cout << a << " " << b << " " << c << std::endl;
}
Ett exempel på att dölja deklarerade variabler före en slinga är:
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
Men om du vill använda den redan deklarerade variabeln och inte dölja den, utelämna deklarationsdelen:
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
Anmärkningar:
- Initierings- och steguppgörelserna kan utföra operationer som inte är relaterade till tillståndsdeklarationen, eller ingenting alls - om du vill göra det. Men av läsbarhetsskäl är det bästa sättet att bara utföra operationer som är direkt relevanta för slingan.
- En variabel som deklareras i initialiseringsförklaringen är synlig endast inom ramen för
for
loopen och släpps vid avslutningen av slingan. - Glöm inte att variabeln som deklarerades i
initialization statement
kan ändras under slingan, liksom variabeln kontrollerad icondition
.
Exempel på en slinga som räknas från 0 till 10:
for (int counter = 0; counter <= 10; ++counter)
{
std::cout << counter << '\n';
}
// counter is not accessible here (had value 11 at the end)
Förklaring av kodfragmenten:
-
int counter = 0
initialiserar den variablacounter
till 0. (Denna variabel kan endast användas insidan avfor
slingan.) -
counter <= 10
är ett booleskt tillstånd som kontrollerar omcounter
är mindre än eller lika med 10. Om det ärtrue
, kör slingan. Om den ärfalse
slutar slingan. -
++counter
är en stegvis operation som ökar värdet påcounter
med 1 före nästa tillståndskontroll.
Genom att lämna alla uttalanden tomma kan du skapa en oändlig slinga:
// infinite loop
for (;;)
std::cout << "Never ending!\n";
Ekvivalent med while
slingan för ovanstående är:
// infinite loop
while (true)
std::cout << "Never ending!\n";
Emellertid kan en oändlig slinga fortfarande lämnas genom att använda uttalanden break
, goto
eller return
eller genom att kasta ett undantag.
Nästa vanliga exempel på att iterera över alla element från en STL-samling (t.ex. en vector
) utan att använda <algorithm>
-huvudet är:
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;
}
Medan slinga
A while
loop exekverar uttalanden tills de givna villkoret utvärderas till false
. Detta kontrolluttalande används när det inte är känt i förväg hur många gånger ett kodblock ska utföras.
Till exempel, för att skriva ut alla siffror från 0 till 9, kan följande kod användas:
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
Observera att sedan C ++ 17 kan de första två påståendena kombineras
while (int i = 0; i < 10)
//... The rest is the same
För att skapa en oändlig slinga kan följande konstruktion användas:
while (true)
{
// Do something forever (however, you can exit the loop by calling 'break'
}
Det finns en annan variant av while
slingor, nämligen do...while
konstruera. Se exempel på do-while-loop för mer information.
Förklaring av variabler i förhållanden
I tillståndet for
och while
slingor är det också tillåtet att förklara ett objekt. Detta objekt kommer att anses vara i omfattning till slutet av slingan och kommer att fortsätta genom varje iteration av slingan:
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.
Det är emellertid inte tillåtet att göra samma sak med en do...while
loop; istället, förklara variabeln innan slingan, och (valfritt) bifoga både variabeln och slingan inom ett lokalt omfång om du vill att variabeln ska gå ut ur räckvidden efter att slingan är slut:
//This doesn't compile
do {
s = do_something();
} while (short s > 0);
// Good
short s;
do {
s = do_something();
} while (s > 0);
Detta beror på att uttalandets del av en do...while
slingan (slingans kropp) utvärderas innan uttrycksdelen ( while
) nås, och alltså kommer alla deklarationer i uttrycket inte att synas under den första iterationen av slinga.
Do-while-loop
En do-while- loop liknar en while- loop, förutom att villkoret kontrolleras i slutet av varje cykel, inte i början. Därför garanteras slingan att köras minst en gång.
Följande kod kommer att skriva ut 0
, eftersom villkoret kommer att utvärderas till false
i slutet av den första iterationen:
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
Obs: Glöm inte semikolon i slutet av while(condition);
, som behövs i do-while- konstruktionen.
Till skillnad från do-while- slingan kommer följande inte att skriva ut något, eftersom villkoret utvärderas till false
i början av den första iterationen:
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
Obs: A while-slinga kan avslutas utan att villkoret blir falskt genom att använda en break
, goto
eller return
uttalande.
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
En trivial do-while- slinga används också ibland för att skriva makroer som kräver sitt eget räckvidd (i vilket fall släpande semikolon utelämnas från makrodefinitionen och som måste tillhandahållas av användaren):
#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-uttalanden: Bryt och fortsätt
Loop-kontrollsatser används för att ändra exekveringsflödet från dess normala sekvens. När exekveringen lämnar ett omfattning förstörs alla automatiska objekt som skapades i det omfånget. break
och continue
är slingkontrolluttalanden.
Den break
uttalande avslutar en loop utan ytterligare behandling.
for (int i = 0; i < 10; i++)
{
if (i == 4)
break; // this will immediately exit our loop
std::cout << i << '\n';
}
Ovanstående kod kommer att skriva ut:
1
2
3
Den continue
uttalandet inte omedelbart avsluta slingan, utan hoppar resten av slingan och går till toppen av slingan (inklusive kontroll av tillstånd).
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";
}
Ovanstående kod kommer att skriva ut:
1 is an odd number
3 is an odd number
5 is an odd number
Eftersom sådana kontrollflödesförändringar ibland är svåra för människor att lätt förstå, används break
och continue
sparsamt. Mer enkel implementering är vanligtvis lättare att läsa och förstå. Till exempel kan den första for
slingan med break
ovan skrivas om som:
for (int i = 0; i < 4; i++)
{
std::cout << i << '\n';
}
Det andra exemplet med continue
kan skrivas om som:
for (int i = 0; i < 6; i++)
{
if (i % 2 != 0) {
std::cout << i << " is an odd number\n";
}
}
Räckvidd för över ett underområde
Med hjälp av områdebasslingor kan du slinga över en underdel av en given behållare eller annat intervall genom att generera ett proxyobjekt som kvalificerar sig för områdebaserade för slingor.
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 kan vi göra:
std::vector<int> v = {1,2,3,4};
for (auto i : except_first(v))
std::cout << i << '\n';
och skriva ut
2
3
4
Var medveten om att mellanliggande objekt som genereras i for(:range_expression)
av for
loopen har löpt ut när tiden for
loopen startar.