Suche…


Einführung

Eine Schleifenanweisung führt eine Gruppe von Anweisungen wiederholt aus, bis eine Bedingung erfüllt ist. Es gibt drei Arten von primitiven Schleifen in C ++: für, while und do ... while.

Syntax

  • while ( Bedingung ) Anweisung ;
  • do- Anweisung while ( Ausdruck );
  • for ( for-init-Anweisung ; Bedingung ; Ausdruck ) Anweisung ;
  • for ( für Range-Deklaration : für Range-Initialisierer ) Anweisung ;
  • brechen ;
  • fortsetzen ;

Bemerkungen

algorithm sind im Allgemeinen handgeschriebenen Schleifen vorzuziehen.

Wenn Sie etwas möchten, das ein Algorithmus bereits tut (oder etwas sehr ähnliches), ist der Algorithmusaufruf klarer, häufig effizienter und weniger fehleranfällig.

Wenn Sie eine Schleife benötigen, die recht einfache Funktionen ausführt (wenn Sie jedoch einen verwirrenden Knoten aus Bindern und Adaptern benötigen, wenn Sie einen Algorithmus verwenden), schreiben Sie einfach die Schleife.

Bereichsabhängig für

C ++ 11

for Schleifen können verwendet werden, um die Elemente eines Iterator-basierten Bereichs zu durchlaufen, ohne einen numerischen Index zu verwenden oder direkt auf die Iteratoren zuzugreifen:

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

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

std::cout << std::endl;

Dadurch wird jedes Element in v durchlaufen, wobei val den Wert des aktuellen Elements erhält. Die folgende Aussage:

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

ist äquivalent zu:

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

Diese Änderung wurde für die geplante Unterstützung von Ranges TS in C ++ 20 eingeführt.

In diesem Fall entspricht unsere Schleife:

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

Beachten Sie, dass auto val einen auto val deklariert, bei dem es sich um eine Kopie eines im Bereich gespeicherten Werts handelt (wir initialisieren ihn vom Iterator aus). Wenn die in diesem Bereich gespeicherten Werte teuer zu kopieren sind, möchten Sie möglicherweise const auto &val . Sie müssen auch nicht auto . Sie können einen geeigneten Typnamen verwenden, sofern dieser implizit aus dem Werttyp des Bereichs konvertierbar ist.

Wenn Sie Zugriff auf den Iterator benötigen, kann Range-based für Sie nicht helfen (zumindest nicht ohne Anstrengung).

Wenn Sie darauf verweisen möchten, können Sie dies tun:

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

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

Sie könnten die const Referenz durchlaufen, wenn Sie über einen const Container verfügen:

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

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

Weiterleitungsreferenzen werden verwendet, wenn der Sequenz-Iterator ein Proxy-Objekt zurückgibt und Sie dieses Objekt auf eine nicht const Weise const . Hinweis: Die Leser Ihres Codes werden höchstwahrscheinlich verwirrt.

vector<bool> v(10);

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

Der „Bereich“ type vorgesehen bereichsbasierte for eine der folgenden sein kann:

  • Spracharrays:

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

    Beachten Sie, dass das Zuordnen eines dynamischen Arrays nicht zählt:

    float *arr = new float[3]{0.4f, 12.5f, 16.234f};
    
    for(auto val: arr) //Compile error.
    {
        std::cout << val << " ";
    }
    
  • Jeder Typ, der die Member-Funktionen begin() und end() , die Iteratoren an die Elemente des Typs zurückgeben. Die Standard-Bibliothekscontainer sind qualifiziert, aber auch benutzerdefinierte Typen können verwendet werden:

    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 << " ";
        }
    }
    
  • Jeder Typ, der über keine begin(type) und end(type) -Funktionen verfügt, die über eine artenabhängige Suche nach type . Dies ist nützlich, wenn Sie einen Bereichstyp erstellen möchten, ohne den Klassentyp selbst ändern zu müssen:

    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 Schleife

A for Schleife führt Anweisungen in dem loop body , während die condition wahr ist. Vor der Schleife wird die initialization statement genau einmal ausgeführt. Nach jedem Zyklus wird der iteration execution ausgeführt.

Eine for Schleife ist wie folgt definiert:

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

Erklärung der Platzhalteraussagen:

  • initialization statement : Diese Anweisung wird nur einmal am Anfang der for Schleife ausgeführt. Sie können eine Deklaration mehrerer Variablen eines Typs eingeben, z. B. int i = 0, a = 2, b = 3 . Diese Variablen sind nur im Geltungsbereich der Schleife gültig. Variablen, die vor der Schleife mit demselben Namen definiert wurden, werden während der Ausführung der Schleife ausgeblendet.
  • condition : Diese Aussage ausgewertet wird vor jedem Schleifenkörper Ausführung, und bricht die Schleife , wenn es wertet false .
  • iteration execution : Diese Anweisung wird nach dem Loop- Body vor der nächsten Bedingungsbewertung ausgeführt , es sei denn, die for Schleife wird im Body abgebrochen (durch break , goto , return oder eine geworfene Ausnahme). Sie können im iteration execution mehrere Anweisungen eingeben, z. B. a++, b+=10, c=b+a .

Das grobe Äquivalent einer for Schleife, die als while Schleife neu geschrieben wurde, lautet:

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

Der häufigste Fall für die Verwendung einer for Schleife ist das Ausführen von Anweisungen zu einer bestimmten Anzahl von Malen. Betrachten Sie zum Beispiel Folgendes:

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

Eine gültige Schleife ist auch:

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

Ein Beispiel für das Ausblenden deklarierter Variablen vor einer Schleife ist:

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

Wenn Sie jedoch die bereits deklarierte Variable verwenden und nicht ausblenden möchten, lassen Sie den Deklarationsteil aus:

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

Anmerkungen:

  • Die Initialisierungs- und Inkrementierungsanweisungen können Operationen ausführen, die nicht mit der Bedingungsanweisung zusammenhängen, oder gar nichts - falls Sie dies wünschen. Aus Gründen der Lesbarkeit empfiehlt es sich jedoch, nur Operationen auszuführen, die für die Schleife direkt relevant sind.
  • Eine in der Initialisierungsanweisung deklarierte Variable ist nur innerhalb des Gültigkeitsbereichs der for Schleife sichtbar und wird bei Beendigung der Schleife freigegeben.
  • Vergessen Sie nicht, dass die in der initialization statement deklarierte Variable während der Schleife sowie die in der condition geprüfte Variable geändert werden condition .

Beispiel für eine Schleife, die von 0 bis 10 zählt:

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

Erklärung der Codefragmente:

  • int counter = 0 initialisiert die Variable counter auf 0 (Diese Variable kann nur innerhalb der verwendet werden for die Schleife.)
  • counter <= 10 ist eine boolesche Bedingung, die prüft, ob der counter kleiner oder gleich 10 ist. Wenn er true , wird die Schleife ausgeführt. Wenn es false , endet die Schleife.
  • ++counter ist eine Inkrementierungsoperation, die den Wert des counter vor der nächsten Zustandsprüfung um 1 erhöht.

Wenn Sie alle Anweisungen leer lassen, können Sie eine Endlosschleife erstellen:

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

Das Äquivalent der while Schleife des obigen ist:

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

Eine Endlosschleife kann jedoch immer noch verlassen werden, indem die Anweisungen break , goto oder return oder eine Ausnahme goto wird.

Das nächste gängige Beispiel für das Durchlaufen aller Elemente einer STL-Sammlung (z. B. eines vector ) ohne Verwendung des Headers <algorithm> ist:

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

While-Schleife

Eine while Schleife führt die Anweisungen wiederholt aus, bis die angegebene Bedingung als false ausgewertet wird. Diese Steueranweisung wird verwendet, wenn im Voraus nicht bekannt ist, wie oft ein Codeblock ausgeführt werden soll.

Um beispielsweise alle Zahlen von 0 bis 9 zu drucken, kann der folgende Code verwendet werden:

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

Beachten Sie, dass die ersten zwei Anweisungen seit C ++ 17 kombiniert werden können

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

Um eine Endlosschleife zu erstellen, kann das folgende Konstrukt verwendet werden:

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

Es gibt eine andere Variante von while Schleifen, nämlich das do...while Konstrukt. Weitere Informationen finden Sie im Do-while-Loop-Beispiel .

Deklaration von Variablen in Bedingungen

Im Zustand der for und while Schleifen darf auch ein Objekt deklariert werden. Dieses Objekt wird bis zum Ende der Schleife als Gültigkeitsbereich betrachtet und bleibt bei jeder Iteration der Schleife bestehen:

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.

Es ist jedoch nicht erlaubt, dasselbe mit einer do...while Schleife auszuführen. Deklarieren Sie stattdessen die Variable vor der Schleife und schließen Sie (optional) sowohl die Variable als auch die Schleife in einen lokalen Gültigkeitsbereich ein, wenn die Variable nach dem Beenden der Schleife außerhalb des Gültigkeitsbereichs sein soll:

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

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

Dies liegt daran, dass der Anweisungsteil einer do...while Schleife (des Körpers der Schleife) ausgewertet wird, bevor der Ausdrucksteil (das while ) erreicht ist. Daher werden Deklarationen im Ausdruck während der ersten Iteration des Befehls nicht sichtbar Schleife.

Do-while-Schleife

Eine do-while- Schleife ist einer while- Schleife sehr ähnlich, mit der Ausnahme, dass die Bedingung am Ende jedes Zyklus überprüft wird, nicht am Anfang. Die Schleife kann daher mindestens einmal ausgeführt werden.

Der folgende Code gibt 0 , da die Bedingung am Ende der ersten Iteration als false ausgewertet wird:

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

Hinweis: Vergessen Sie nicht das Semikolon am Ende von while(condition); , was im do-while- Konstrukt benötigt wird.

Im Gegensatz zur Do-while- Schleife wird Folgendes nicht gedruckt, da die Bedingung zu Beginn der ersten Iteration als false ausgewertet wird:

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

Hinweis: Eine while- Schleife kann beendet werden, ohne dass die Bedingung durch eine break , goto oder return goto falsch wird.

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

Eine triviale Do-while- Schleife wird gelegentlich auch zum Schreiben von Makros verwendet, die einen eigenen Gültigkeitsbereich erfordern (in diesem Fall wird das nachstehende Semikolon aus der Makrodefinition weggelassen und muss vom Benutzer bereitgestellt werden)

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

Anweisungen zur Schleifensteuerung: Break and Continue

Schleifensteueranweisungen werden verwendet, um den Ablauf der Ausführung von seiner normalen Reihenfolge zu ändern. Wenn die Ausführung einen Bereich verlässt, werden alle automatischen Objekte, die in diesem Bereich erstellt wurden, zerstört. Das break und continue sind Schleifensteueranweisungen.

Die break Anweisung beendet eine Schleife ohne weitere Überlegung.

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

Der obige Code wird ausgedruckt:

1
2
3

Die continue Anweisung beendet die Schleife nicht sofort, sondern überspringt den Rest des Schleifenkörpers und geht zum Anfang der Schleife (einschließlich der Überprüfung der Bedingung).

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

Der obige Code wird ausgedruckt:

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

Da solche Kontrollflussänderungen für den Menschen manchmal schwer zu verstehen sind, werden break und continue sparsam eingesetzt. Eine einfachere Implementierung ist normalerweise einfacher zu lesen und zu verstehen. Beispielsweise könnte die erste for Schleife mit der obigen break folgendermaßen umgeschrieben werden:

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

Das zweite Beispiel mit continue kann wie folgt umgeschrieben werden:

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

Range-für über einen Unterbereich

Mithilfe von Bereichs-Basis-Schleifen können Sie einen Teilbereich eines bestimmten Containers oder einen anderen Bereich durchlaufen, indem Sie ein Proxy-Objekt generieren, das für Bereichs-basierte Schleifen geeignet ist.

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

Jetzt können wir tun:

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

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

und ausdrucken

2
3
4

for(:range_expression) Sie, dass die im for(:range_expression) der for Schleife generierten Zwischenobjekte zu dem Zeitpunkt abgelaufen sind, zu dem die for Schleife beginnt.



Modified text is an extract of the original Stack Overflow Documentation
Lizenziert unter CC BY-SA 3.0
Nicht angeschlossen an Stack Overflow