Szukaj…


Wprowadzenie

Instrukcja pętli wykonuje grupę instrukcji wielokrotnie, aż do spełnienia warunku. Istnieją 3 typy prymitywnych pętli w C ++: for, while i do ... while.

Składnia

  • instrukcja while ( warunek );
  • wykonaj instrukcję while ( wyrażenie );
  • dla (for-Init-oświadczenie; warunek; wyrażenie) instrukcja;
  • instrukcja for ( for-range-deklaration : for-range-initializer );
  • przerwa ;
  • kontyntynuj ;

Uwagi

wywołania algorithm są na ogół lepsze niż ręcznie pisane pętle.

Jeśli chcesz czegoś, co algorytm już robi (lub czegoś bardzo podobnego), wywołanie algorytmu jest wyraźniejsze, często bardziej wydajne i mniej podatne na błędy.

Jeśli potrzebujesz pętli, która robi coś dość prostego (ale wymagałaby mylącego plątania segregatorów i adapterów, jeśli korzystasz z algorytmu), po prostu napisz pętlę.

Na podstawie zakresu dla

C ++ 11

for pętli można używać do iteracji elementów zakresu opartego na iteratorze, bez użycia indeksu numerycznego lub bezpośredniego dostępu do iteratorów:

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

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

std::cout << std::endl;

Spowoduje to iterację każdego elementu w v , a val otrzyma wartość bieżącego elementu. Następujące oświadczenie:

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

jest równa:

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

Ta zmiana została wprowadzona w związku z planowanym wsparciem dla Ranges TS w C ++ 20.

W takim przypadku nasza pętla odpowiada:

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

Zauważ, że auto val deklaruje typ wartości, który będzie kopią wartości przechowywanej w zakresie (inicjalizujemy kopiowanie z iteratora w trakcie pracy). Jeśli wartości przechowywane w zakresie są kosztowne do skopiowania, możesz użyć const auto &val . Nie musisz także używać auto ; możesz użyć odpowiedniej nazwy typu, o ile jest ona domyślnie konwertowana z typu wartości zakresu.

Jeśli potrzebujesz dostępu do iteratora, funkcja oparta na zakresie nie może ci pomóc (przynajmniej bez pewnego wysiłku).

Jeśli chcesz się do niego odwołać, możesz to zrobić:

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

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

Można iteracyjne na const odniesienia, jeśli masz const pojemnik:

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

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

Można by użyć referencji przekazujących, gdy iterator sekwencji zwraca obiekt proxy i trzeba operować na tym obiekcie w sposób const . Uwaga: najprawdopodobniej dezorientuje czytelników twojego kodu.

vector<bool> v(10);

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

Typ „zasięgu” dla parametru opartego for może być jednym z następujących:

  • Tablice językowe:

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

    Pamiętaj, że przydzielenie tablicy dynamicznej nie ma znaczenia:

    float *arr = new float[3]{0.4f, 12.5f, 16.234f};
    
    for(auto val: arr) //Compile error.
    {
        std::cout << val << " ";
    }
    
  • Każdy typ, który ma funkcje begin() i end() , które zwracają iteratory do elementów tego typu. Standardowe kontenery biblioteczne kwalifikują się, ale można również użyć typów zdefiniowanych przez użytkownika:

    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 << " ";
        }
    }
    
  • Dowolny typ, który ma funkcje begin(type) i end(type) które nie są członkami, które można znaleźć poprzez wyszukiwanie zależne od argumentów, w zależności od type . Jest to przydatne do tworzenia typu zakresu bez konieczności modyfikacji samego typu klasy:

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

Dla pętli

Pętla for wykonuje instrukcje w loop body , podczas gdy condition pętli jest prawdziwy. Zanim instrukcja initialization statement pętli zostanie wykonana dokładnie raz. Po każdym cyklu iteration execution jest część iteration execution .

Pętla for jest zdefiniowana następująco:

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

Objaśnienie instrukcji zastępczych:

  • initialization statement : Ta instrukcja jest wykonywana tylko raz, na początku pętli for . Możesz wprowadzić deklarację wielu zmiennych jednego typu, takich jak int i = 0, a = 2, b = 3 . Te zmienne są poprawne tylko w zakresie pętli. Zmienne zdefiniowane przed pętlą o tej samej nazwie są ukryte podczas wykonywania pętli.
  • condition : instrukcja ta jest oceniana przed wykonaniem każdego elementu pętli i przerywa pętlę, jeśli ma wartość false .
  • iteration execution : Ta instrukcja jest wykonywana po treści pętli, przed następną oceną warunku , chyba że pętla for zostanie przerwana w ciele (przez break , goto , return lub zgłoszony wyjątek). Możesz wprowadzić wiele instrukcji w części iteration execution , takich jak a++, b+=10, c=b+a .

Surowa odpowiednik for pętli przepisany jako while pętla jest:

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

Najczęstszym przypadkiem użycia pętli for jest wykonywanie instrukcji określoną liczbę razy. Rozważ na przykład:

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

Prawidłowa pętla to również:

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

Przykładem ukrywania zadeklarowanych zmiennych przed pętlą jest:

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

Ale jeśli chcesz użyć już zadeklarowanej zmiennej i nie ukrywać jej, to pomiń część deklaracyjną:

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

Uwagi:

  • Instrukcje inicjalizacji i inkrementacji mogą wykonywać operacje niezwiązane z instrukcją warunku lub w ogóle nic - jeśli chcesz to zrobić. Jednak ze względu na czytelność najlepszą praktyką jest wykonywanie operacji bezpośrednio związanych z pętlą.
  • Zmienna zadeklarowana w instrukcji inicjalizacji jest widoczna tylko w zakresie pętli for i jest zwalniana po zakończeniu pętli.
  • Nie zapominaj, że zmienna zadeklarowana w instrukcji initialization statement może być modyfikowana podczas pętli, a także zmienna sprawdzana w condition .

Przykład pętli liczącej od 0 do 10:

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

Objaśnienie fragmentów kodu:

  • int counter = 0 inicjuje counter zmiennej na 0. (Tej zmiennej można używać tylko wewnątrz pętli for .)
  • counter <= 10 to warunek logiczny, który sprawdza, czy counter jest mniejszy lub równy 10. Jeśli to true , pętla jest wykonywana. Jeśli jest to false , pętla się kończy.
  • ++counter to operacja inkrementacji, która zwiększa wartość counter o 1 przed następnym sprawdzaniem warunków.

Pozostawiając wszystkie instrukcje puste, możesz utworzyć nieskończoną pętlę:

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

while odpowiednik pętli powyższego jest następujący:

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

Jednak nieskończoną pętlę można nadal opuścić, używając instrukcji break , goto lub return lub zgłaszając wyjątek.

Następnym częstym przykładem iteracji po wszystkich elementach z kolekcji STL (np. vector ) bez użycia nagłówka <algorithm> jest:

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

Podczas pętli

while wykonuje pętli wielokrotnie oświadczenia aż podanych analizuje schemat Warunkiem false . Ta instrukcja sterująca jest używana, gdy nie wiadomo z góry, ile razy blok kodu ma zostać wykonany.

Na przykład, aby wydrukować wszystkie liczby od 0 do 9, można użyć następującego kodu:

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

Zauważ, że od C ++ 17 pierwsze 2 instrukcje można łączyć

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

Aby utworzyć nieskończoną pętlę, można użyć następującej konstrukcji:

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

Istnieje inny wariant pętli while , a mianowicie konstrukcja do...while . Zobacz przykład pętli „do-while”, aby uzyskać więcej informacji.

Deklaracja zmiennych w warunkach

W warunkach pętli for i while dozwolone jest także deklarowanie obiektu. Ten obiekt będzie uważany za objęty zakresem do końca pętli i będzie trwał przez każdą iterację pętli:

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.

Jednak nie można robić tego samego z pętlą do...while while; zamiast tego zadeklaruj zmienną przed pętlą i (opcjonalnie) dołącz zarówno zmienną, jak i pętlę do zakresu lokalnego, jeśli chcesz, aby zmienna wychodziła poza zakres po zakończeniu pętli:

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

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

Wynika to z tego, że część instrukcji pętli do...while while (ciało pętli) jest oceniana przed osiągnięciem części wyrażenia ( while ), a zatem żadna deklaracja w wyrażeniu nie będzie widoczna podczas pierwszej iteracji pętla.

Pętla „do-while”

Do-while jest bardzo podobna do pętli while, z wyjątkiem, że warunek jest sprawdzany na końcu każdego cyklu, a nie na początku. Pętla gwarantuje zatem wykonanie co najmniej raz.

Poniższy kod wyświetli 0 , ponieważ warunek będzie miał wartość false na końcu pierwszej iteracji:

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

Uwaga: Nie zapomnij o średniku na końcu while(condition); , co jest potrzebne w konstrukcji do-while .

W przeciwieństwie do pętli „ do- while”, poniższe polecenie nic nie wydrukuje, ponieważ warunek ma wartość false na początku pierwszej iteracji:

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

Uwaga: podczas gdy pętla może zostać zakończony bez warunek staje się fałszywy, stosując break , goto , lub return oświadczenie.

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

Trywialna pętla „ do- while” jest również czasami używana do pisania makr, które wymagają własnego zakresu (w takim przypadku średnik końcowy jest pomijany w definicji makra i musi być dostarczony przez użytkownika):

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

Instrukcje kontroli pętli: Przerwij i kontynuuj

Instrukcje sterujące pętlą służą do zmiany przepływu wykonania z jego normalnej sekwencji. Gdy wykonanie opuszcza zakres, wszystkie obiekty automatyczne utworzone w tym zakresie są niszczone. break i continue są instrukcjami sterującymi pętli.

Instrukcja break kończy pętlę bez dalszego rozpatrywania.

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

Powyższy kod zostanie wydrukowany:

1
2
3

Instrukcja continue nie kończy natychmiast pętli, lecz pomija resztę treści pętli i przechodzi na jej szczyt (łącznie ze sprawdzeniem warunku).

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

Powyższy kod zostanie wydrukowany:

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

Ponieważ takie zmiany w przepływie kontroli są czasem trudne do zrozumienia dla ludzi, break i continue są stosowane oszczędnie. Prostsza implementacja jest zwykle łatwiejsza do odczytania i zrozumienia. Na przykład pierwsza pętla for z powyższym break może zostać przepisana jako:

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

Drugi przykład z continue można przepisać jako:

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

Zasięg dla ponad zakresu

Korzystając z pętli bazowych zakresu, można zapętlać pod-część danego kontenera lub innego zakresu, generując obiekt proxy, który kwalifikuje się do pętli opartych na zakresie.

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

teraz możemy zrobić:

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

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

i wydrukuj

2
3
4

Należy pamiętać, że obiekty pośrednie wygenerowane w części for(:range_expression) pętli for wygasną do momentu uruchomienia pętli for .



Modified text is an extract of the original Stack Overflow Documentation
Licencjonowany na podstawie CC BY-SA 3.0
Nie związany z Stack Overflow