Serwis Edukacyjny
w I-LO w Tarnowie
obrazek

Materiały dla uczniów liceum

  Wyjście       Spis treści       Wstecz       Dalej  

Autor artykułu: mgr Jerzy Wałaszek
Zmodyfikowano 14.12.2022

©2023 mgr Jerzy Wałaszek
I LO w Tarnowie

Materiały do matury z informatyki

C++ - pętla warunkowa

SPIS TREŚCI

Pętla while

W języku programowania pętla (ang. loop) jest instrukcją lub blokiem instrukcji, które komputer cyklicznie wykonuje. Pojedyncze wykonanie tych instrukcji nazywamy obiegiem pętli. To dzięki pętlom programy mogą się wykonywać przez dowolny czas. Gdyby nie było pętli, wykonanie programu skończyłoby się po wykonaniu kolejnych instrukcji, czyli praktycznie błyskawicznie, gdyż współczesne komputery pracują bardzo szybko

Jeśli wykonanie obiegu pętli zależy od pewnego warunku logicznego, to mamy pętlę warunkową (ang. conditional loop).

W języku C++ mamy dwie instrukcje pętli warunkowej. Pierwsza z nich to pętla while (dopóki) o następującej składni:

while(warunek) instrukcja;

lub

while(warunek)
{
    instrukcja_1;
    instrukcja_2;
    ...
    instrukcja_n;
}

Działanie jest następujące:

Przed każdym obiegiem (czyli wykonaniem instrukcji lub bloku) komputer wylicza wartość logiczną warunku. Jeśli wynikiem jest prawda, to zostaje wykonany jeden obieg i następuje powrót do ponownego wyznaczenia warunku. Komputer cyklicznie wykonuje obiegi pętli, aż otrzyma wartość fałszywą warunku - dlatego warunek ten często nazywa się warunkiem kontynuacji, ponieważ obiegi są kontynuowane, gdy warunek jest prawdziwy, a przerywane, gdy jest fałszywy. Wtedy komputer przechodzi do kolejnej instrukcji w programie.

Uwaga: spotkałem się z przypadkiem, iż niektórzy uczniowie mylą ze sobą instrukcje if i while, ponieważ posiadają one podobną składnię:

if(warunek) instrukcja;     while(warunek) instrukcja;
if(warunek)
{
    blok instrukcji
}
  while(warunek)
{
    blok instrukcji
}

Różnica jest jednak istotna: instrukcja if wykonuje jeden raz instrukcję/blok przy warunku prawdziwym, po czym komputer przechodzi do następnych instrukcji w programie, natomiast instrukcja while wykonuje cyklicznie instrukcję/blok dopóki warunek jest prawdziwy przed wykonaniem obiegu. Gdy warunek stanie się fałszywy, wykonywanie obiegów jest przerywane i dopiero wtedy komputer przechodzi do następnych instrukcji w programie.

Zauważ, iż ponieważ warunek w instrukcji while jest sprawdzany na początku przed wykonaniem obiegu, to jeśli warunek ten będzie fałszywy przy wejściu do pętli, to pętla nie wykona ani jednego obiegu – zapamiętaj ten fakt, jest ważny!

Teraz kilka praktycznych zastosowań pętli while.

Program wyświetla zadaną liczbę kolejnych liczb naturalnych:

// Pętla warunkowa while
//----------------------

#include <iostream>

using namespace std;

int main()
{
    setlocale(LC_ALL,"");

    unsigned i = 1,n; // Zmienne całkowite nieujemne

    cout << "Ile liczb? : "; cin >> n;

    while(i <= n) cout << i++ << " ";

    cout << endl << endl;

    return 0;
}

W programie tworzymy 2 zmienne: i dla kolejnych liczb naturalnych oraz n dla ich ilości. Zmienną i ustawiamy na pierwszą liczbę naturalną: 1. Następnie odczytujemy wartość n. Tworzymy pętlę while, która będzie wykonywana dopóki liczba naturalna w zmiennej i nie przekroczy wartości n (warunek i <= n). W pętli wyświetlamy wartość wyrażenia i++. Wyrażenie to zwraca bieżącą wartość zmiennej i, po czym operator ++ zwiększa zawartość zmiennej i o 1. Zatem w pierwszym obiegu pętla wyświetli 1, a zmienna i zostanie zwiększona do 2. W drugim obiegu pętla wyświetli 2, a i zwiększy do 3, i tak dalej aż zmienna i przekroczy wartość pamiętaną w zmiennej n. Wtedy pętla zakończy powtarzanie. W wyniku w oknie konsoli otrzymamy ciąg kolejnych liczb naturalnych od 1 do n.

Ile liczb? : 25
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25

Następny program wyświetla liczby naturalne w odwrotnej kolejności:

// Pętla warunkowa while
//----------------------

#include <iostream>

using namespace std;

int main()
{
    setlocale(LC_ALL,"");

    unsigned i; //Liczba naturalna

    cout << "Ile liczb? : "; cin >> i;

    while(i) cout << i-- << " ";

    cout << endl << endl;

    return 0;
}

Zwróć uwagę, iż program jest prostszy od poprzedniego, ponieważ nie musimy pamiętać w osobnej zmiennej granicy końcowej (zaraz wyjaśnimy dlaczego). W programie tworzymy tylko jedną zmienną i na liczby naturalne. Do tej zmiennej odczytujemy wartość początkową, od której program rozpocznie wyświetlanie ciągu. Tworzymy pętlę while. Warunkiem jest wartość i traktowana jako wartość logiczna, a to oznacza, iż warunek będzie prawdziwy, gdy i będzie różne od 0 i fałszywy dla i równego zero. W pętli wyświetlamy wartość wyrażenia i--. Wyrażenie zwraca bieżącą wartość zmiennej i, po czym operator -- zmniejsza i o 1. Jeśli do i wprowadzimy np. 10, to w pierwszym obiegu pętla wyświetli 10, po czym zmniejszy i do 9, w drugim obiegu wyświetli 9 i zmniejszy i do 8. Będzie to poważane aż do ostatniego obiegu, w którym i osiągnie 1. Komputer wyświetli 1, po czym zmniejszy i do 0. W następnym obiegu warunek kontynuacji przestanie być prawdziwy i pętla zakończy się.

Ile liczb? : 25
25 24 23 22 21 20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1

W programie pętlę:

while(i) cout << i-- << " ";

możemy również zapisać jako:

while(i--) cout << i << " ";

Kolejny program znajduje sumę kolejnych liczb naturalnych, która przekracza zadaną granicę. Algorytm jest następujący:

Wejście: granica g, którą ma przekroczyć suma liczb naturalnych.
Wyjście: wartość pierwszej sumy s kolejnych liczb naturalnych, która przekracza granicę.

Lista kroków:

K1: i ← 1
K2: s ← 0
K3: Czytaj g
K4: Dopóki s ≤ g, wykonuj K5...K6
K5:    s ← s + i
K6:    i ← i + 1
K7: Pisz s
K8: Zakończ

Schemat blokowy:

Pseudokod:

i ← 1
s ← 0
czytaj g
dopóki s ≤ g:
    s ← s + i
    i ← i + 1
pisz s

Program w C++:

// Pętla warunkowa while
//----------------------

#include <iostream>

using namespace std;

int main()
{
    setlocale(LC_ALL,"");

    unsigned i = 1,s = 0,g; // Zmienne całkowite nieujemne

    cout << "Suma kolejnych liczb naturalnych" << endl
         << "aż do przekroczenia granicy g" << endl
         << "--------------------------------" << endl << endl
         << "Podaj g = ";

    cin >> g;

    while(s <= g)
    {
        s = s + i; // Sumujemy liczbę naturalną
        i++;       // Kolejna liczba naturalna
    }

    cout << endl << "Suma  s = " << s << endl << endl;

    return 0;
}

Zwróć uwagę, iż pętlę można uprościć z:

while(s <= g)
{
    s = s + i;
    i++; 
}

na:

while(s <= g) s = s + i++;

Wyjaśnij to uproszczenie.

Zadanie:

Zmodyfikuj pierwszy program tak, aby wyświetlał zadaną ilość kolejnych liczb: a) parzystych, b) nieparzystych, c) wielokrotności liczby 5.

Do zapamiętania:

Na początek:  podrozdziału   strony 

Pętla do...while

Druga pętla warunkowa posiada następującą składnię:

do instrukcja; while(warunek);

do
{
    instrukcja_1;
    instrukcja_2;
    ...
    instrukcja_n;
} while (warunek);

Zasada działania jest następująca:

Komputer najpierw wykonuje obieg pętli, a następnie sprawdza warunek. Jeśli jest on prawdziwy, to następuje powrót na początek pętli i wykonanie kolejnego obiegu. Jeśli warunek jest fałszywy, komputer kończy pętlę i przechodzi do następnej instrukcji w programie.

Ponieważ obieg pętli (instrukcja lub blok instrukcji) jest wykonywany przed testem, to pierwszy obieg zostanie zawsze wykonany w pętli do ...while, bez względu na wartość logiczną warunku (nawet, jeśli jest on fałszywy).

Po co nam taka instrukcja? Otóż okazuje się, iż czasem warunek sprawdzany przez pętlę zależy od instrukcji wykonywanych w obiegu tej pętli. Nie można go zatem sprawdzać przed wykonaniem obiegu, lecz dopiero po. Na przykład poniższy program:

// Pętla warunkowa do while
//-------------------------

#include <iostream>

using namespace std;

int main()
{
    setlocale(LC_ALL,"");

    int kod;

    do
    {
        cout << "Wprowadź poprawny kod: ";
        cin  >> kod;
    } while(kod != 1234);

    cout << "Kod przyjęty" << endl << endl;

    return 0;
}
Wprowadź poprawny kod: 120
Wprowadź poprawny kod: 256
Wprowadź poprawny kod: 1024
Wprowadź poprawny kod: 1234
Kod przyjęty

W programie tworzymy zmienną całkowitą kod, po czym w pętli odczytujemy do tej zmiennej liczbę. Pętla jest kontynuowana, jeśli wprowadzona liczba różni się od 1234. Zatem dopóki użytkownik nie wprowadzi tej liczby, jest uwięziony w pętli do...while.

Pętla do...while jest nieco trudniejsza w użyciu, jednakże zwykle można ją stosować zamiennie z pętlą while, gdy pierwszy obieg jest zawsze wykonywany. Poniżej kilka przykładowych programów:

// Pętla warunkowa do while
//-------------------------

#include <iostream>

using namespace std;

int main()
{
    setlocale(LC_ALL,"");

    int i = 1,n;

    cout << "Ile liczb? : "; cin >> n;
    do cout << i++ << " "; while(i <= n);

    cout << endl << endl;

    return 0;
}

Program wyświetla kolejne liczby naturalne od 1 do n. Wartość n jest odczytywana z klawiatury.

Ile liczb? : 15
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15

Zwróć uwagę, iż jeśli wprowadzisz jako n liczbę 0, to:

Ile liczb? : 0
1

Pojawia się liczba 1, ponieważ wyświetlana jest ona w pierwszym obiegu pętli, a pierwszy obieg w do..while jest wykonywany zawsze.


To samo dla liczb w kierunku odwrotnym:

// Pętla warunkowa do while
//-------------------------

#include <iostream>

using namespace std;

int main()
{
    setlocale(LC_ALL,"");

    int i;

    cout << "Ile liczb? : "; cin >> i;
    do cout << i-- << " "; while(i);

    cout << endl << endl;

    return 0;
}
Ile liczb? : 18
18 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1

I tutaj uważaj, jeśli wpiszesz 0, program wejdzie w dosyć długą pętlę:

Ile liczb? : 0
0 4294967295 4294967294 4294967293 ...

Dlaczego? W pierwszym obiegu zmienna i zostaje zmniejszona o 1 przyjmie wartość... no jaką? Dla prostoty załóżmy, że i jest zmienną 4 bitową w kodzie NBC (to jest właśnie typ unsigned). Ponieważ wpisano 0 do i, to zmienna ta zawiera liczbę 0000NBC. Odejmujemy 1. Odjęcie jest równoważne z dodaniem liczby przeciwnej. 1 w kodzie NBC ma postać 0001NBC. Liczbą przeciwną (zobacz tutaj, jeśli nie pamiętasz) jest 1111NBC. Dodajemy:

               
  0 0 0 0     0
+ 1 1 1 1   + (-1)
  1 1 1 1 =   15

Otrzymaliśmy liczbę 15, czyli największą 4-bitową wartość NBS ("licznik" się po prostu przekręcił). Zwróć uwagę, iż w systemie U2 liczba ta miałaby wartość -1, czyli prawidłową. Ponieważ pracujemy w NBS, to liczbę interpretujemy jako NBS 15. Zatem w pierwszym obiegu zmienna i otrzymuje wartość maksymalną NBS. U nas zmienna jest 32-bitowa, więc jej wartość maksymalna to 232 - 1 = 4294967295. I ta wartość jest zmniejszana w kolejnych obiegach do 0, wtedy dopiero pętla się zatrzyma po wykonaniu 4294967296 obiegów!

Zmień typ zmiennej i z unsigned na int. Teraz pracujemy z liczbami U2. Po wprowadzeniu 0 otrzymasz:

Ile liczb? : 0
0 -1 -2 -3 ...

Przeanalizujmy to na 4-bitowych liczbach U2 (pierwszy mikroprocesor Intel 4004 pracował właśnie na liczbach 4-bitowych). 0 = 0000U2, -1 = 1111U2. Po dodaniu -1 zmienna i maleje, aż osiągnie najmniejszą wartość ujemną 1000U2 = -8. Dodajmy do tej wartości nasze -1 (reguły dodawania nie zależą od wartości dodawanych liczb):

1              
  1 0 0 0     -8
+ 1 1 1 1   + (-1)
  0 1 1 1 =   +7

Otrzymaliśmy błędny wynik +7, ponieważ wynik odejmowania wyszedł poza zakres 4-bitowych liczb U2 (-8...7), potrzebny byłby kod 5-bitowy na poprawne przedstawienie liczby -9 (10111U2 = -16 + 7 = -9). Wartość ta będzie dalej zmniejszana, aż do 0. W efekcie pętla z takim typem danych wykonałaby 16 obiegów i wyświetliłaby liczby:

0 -1 -2 -3 -4 -5 -6 -7 -8 +7 +6 +5 +4 +3 +2 +1

czyli 24 , gdzie 4 jest liczbą bitów w wybranym formacie U2. W naszym programie typ int oznacza 32-bitową liczbę U2, czyli pętla do...while wykona 232 = 4294967296 obiegów, tyle samo, co poprzednio.

Znajomość kodowania liczb oraz zasad wykonywania na nich działań pozwala dobremu programiście unikać takich pułapek. Ogólnie przy dodawaniu liczb U2 tego samego znaku wynik jest błędny, jeśli posiada znak do nich przeciwny: w naszym przykładzie dla 4-bitowych liczb U2 wystąpiło (-8)  + (-1) = +7,  (-) + (-) → (+).


Program oblicza sumę zadanej ilości kolejnych liczb nieparzystych. Na przykład suma kolejnych 5 liczb nieparzystych wyniesie:

1 + 3 + 5 + 7 + 9 = 25

Tutaj również musisz uważać na wprowadzenie liczby 0. Z pętlą while ten problem nie występuje.

// Pętla warunkowa do while
//-------------------------

#include <iostream>

using namespace std;

int main()
{
    setlocale(LC_ALL,"");

    int i = 1, s = 0, n;

    cout << "Suma n kolejnych liczb nieparzystych" << endl
         << "------------------------------------" << endl << endl
         << "Wprowadź n = ";
    cin >> n;

    do
    {
        s = s + i;
        i = i + 2;
        n--;
    } while(n);

    cout << "Suma wynosi: " << s << endl << endl;

    return 0;
}

Zadanie

Przerób program tak, aby pokazywał sumowane liczby:

Suma n kolejnych liczb nieparzystych
------------------------------------

Wprowadź n = 10
1 + 3 + 5 + 7 + 9 + 11 + 13 + 15 + 17 + 19 = 100

Do zapamiętania

Na początek:  podrozdziału   strony 

Pętla for

Iteracja (ang. iteration) to w informatyce numerowany obieg pętli. Co to znaczy? Jeśli pętla wykonuje obiegi, to każdy jej obieg posiada swój oddzielny numer informujący komputer (a właściwie informujący algorytm, bo komputerowi wszystko jedno), który obieg aktualnie realizuje. Do przechowywania numeru obiegu pętli zawsze potrzebna jest zmienna, zwana licznikiem pętli (ang. loop counter). Pętla numerująca swoje obiegi nazywana jest pętlą iteracyjną (ang. iteration loop).

Pętlę iteracyjną możemy utworzyć np. przy pomocy pętli warunkowej while. Uruchom program:

// Pętla iteracyjna
//-----------------

#include <iostream>

using namespace std;

int main()
{
    setlocale(LC_ALL,"");

    int i, n;

    cout << "Pętla iteracyjna" << endl
         << "----------------" << endl << endl
         << "Ile obiegów pętli? : ";
    cin >> n;
    cout << endl;

    i = 1;        // 1) INICJALIZACJA LICZNIKA

    while(i <= n) // 2) WARUNEK KONTYNUACJI
    {
        cout << "Obieg nr " << i << endl;
        i++;      // 3) MODYFIKACJA LICZNIKA
    }

    cout << endl << endl;

    return 0;
}
Pętla iteracyjna
----------------

Ile obiegów pętli? : 10

Obieg nr 1
Obieg nr 2
Obieg nr 3
Obieg nr 4
Obieg nr 5
Obieg nr 6
Obieg nr 7
Obieg nr 8
Obieg nr 9
Obieg nr 10

Zauważ, iż pętla iteracyjna składa się z trzech głównych elementów:

  1. Przed rozpoczęciem pętli musi zostać ustawiona odpowiednio zmienna licznikowa. Wpisuje się do niej numer pierwszego obiegu pętli (niekoniecznie 1, może być to dowolny numer, od którego rozpocznie się numeracja obiegów).
  2. Pętla warunkowa while, w której sprawdzamy warunek kontynuacji. Jeśli warunek jest fałszywy, pętla kończy działanie.
  3. Na końcu każdego obiegu modyfikujemy zmienną licznikową tak, aby w następnym obiegu pętli zawierała numer tego obiegu.

Ponieważ tego typu konstrukcja jest bardzo często używana w programowaniu, w języku C++ istnieje specjalna instrukcja do tworzenia tego rodzaju pętli. Posiada ona następującą składnię:

for(inicjalizacja; warunek; modyfikacja) instrukcja;

for(inicjalizacja; warunek; modyfikacja)
{
    instrukcja_1;
    instrukcja_2;
    ...
    instrukcja_n;
}

Instrukcja for odpowiada dokładnie przedstawionemu wcześniej fragmentowi z instrukcją while. Zaletą instrukcji for jest zgrupowanie wszystkich trzech elementów sterujących pętlą w jednym miejscu, co znakomicie przydaje się, jeśli pętla zawiera dużą liczbę wierszy z instrukcjami (nie musimy wtedy ciągle przewijać zawartości okna edytora, aby zobaczyć początek/koniec pętli).

Nasz program z instrukcją for będzie wyglądał teraz tak:

// Pętla iteracyjna
//-----------------

#include <iostream>

using namespace std;

int main()
{
    setlocale(LC_ALL,"");

    int i, n;

    cout << "Pętla iteracyjna" << endl
         << "----------------" << endl << endl
         << "Ile obiegów pętli? : ";
    cin >> n;
    cout << endl;

    for(i = 1; i <= n; i++)
        cout << "Obieg nr " << i << endl;

    cout << endl << endl;

    return 0;
}

Jak widzisz, program się uprościł.

Numery obiegów pętli nie muszą być kolejnymi liczbami naturalnymi. To od nas zależy, jakie numery im nadamy:

// Pętla iteracyjna
//-----------------

#include <iostream>

using namespace std;

int main()
{
    setlocale(LC_ALL,"");

    int i;

    for(i = 10; i; i--) cout << i << " ";

    cout << endl << endl;

    return 0;
}
10 9 8 7 6 5 4 3 2 1
// Pętla iteracyjna
//-----------------

#include <iostream>
#include <iomanip>

using namespace std;

int main()
{
    setlocale(LC_ALL,"");

    cout << fixed << setprecision(2);

    double x;

    for(x = 0; x <= 2.5; x = x + 0.25) cout << x << "  ";

    cout << endl << endl;

    return 0;
}
0.00  0.25  0.50  0.75  1.00  1.25  1.50  1.75  2.00  2.25  2.50

A oto dwa proste programy wykorzystujące pętle iteracyjne:

// Pętla iteracyjna
//-----------------

#include <iostream>

using namespace std;

int main()
{
    setlocale(LC_ALL,"");

    unsigned i, n;

    cout << "Ile x-sów ? : ";
    cin  >> n;
    cout << endl;

    for(i = 0; i < n; i++) cout << "X";

    cout << endl << endl;

    return 0;
}
Ile x-sów ? : 8

XXXXXXXX
// Pętla iteracyjna
//-----------------

#include <iostream>

using namespace std;

int main()
{
    setlocale(LC_ALL,"");

    unsigned i, n, p = 1;

    cout << "Kolejne potęgi liczby 2 od 0 do n" << endl
         << "---------------------------------" << endl << endl
         << "n = ";
    cin  >> n;
    cout << endl;

    for(i = 0; i <= n; i++)
    {
        cout << "2^" << i << " = " << p << endl;
        p = p + p;
    }

    cout << endl << endl;

    return 0;
}
Kolejne potęgi liczby 2 od 0 do n
---------------------------------

n = 5

2^0 = 1
2^1 = 2
2^2 = 4
2^3 = 8
2^4 = 16
2^5 = 32

Zadania:

1. Napisz program, który wyświetli zadaną liczbę wyrazów ciągu liczbowego: 1 2 3 4 5 1 2 3 4 5 1 2...

Rozwiązanie:

Ciąg tworzą kolejne liczby 1 2 3 4 5. Po osiągnięciu 5 następną wartością jest z powrotem 1 i ciąg się powtarza. Rozwiązanie tego typu problemów rozpoczynamy od stworzenia pętli iteracyjnej, która będzie się wykonywać zadaną liczbę razy:
...

int i, n;              // Licznik i liczba obiegów

cin >> n;              // Odczytujemy liczbę obiegów

for(i = 0; i < n; i++) // Pętla wykonuje się n razy
{
    ...
}

...

W języku C++ obiegi pętli najczęściej numeruje się od 0 (powód wyjaśnimy później). Dlatego pętla for wykonująca się n razy ma postać:

for(i = 0; i < n; i++) ...

Teraz tworzymy zmienną na wyrazy ciągu, nadajemy jej wartość początkową 1 i w pętli wyświetlamy:
...

int i, n;              // Licznik i liczba obiegów

cin >> n;              // Odczytujemy liczbę obiegów

int a = 1;             // Pierwszy wyraz ciągu

for(i = 0; i < n; i++) // Pętla wykonuje się n razy
{
    cout << a << " ";
    ...
}

...

Wyliczamy następny wyraz ciągu a i sprawdzamy, czy przekroczył 5. Jeśli tak, to nadajemy mu z powrotem wartość 1:
...

int i, n;              // Licznik i liczba obiegów

cin >> n;              // Odczytujemy liczbę obiegów

int a = 1;             // Pierwszy wyraz ciągu

for(i = 0; i < n; i++) // Pętla wykonuje się n razy
{
    cout << a << " ";
    a++;               // Następny wyraz
    if(a > 5) a = 1;   // Warunek
}

...

Mamy gotowy program:

// Pętla iteracyjna
//-----------------

#include <iostream>

using namespace std;

int main()
{
    setlocale(LC_ALL,"");

    int i, n;

    cout << "Ile wyrazów ciągu? : ";
    cin >> n;
    cout << endl;

    int a = 1;

    for(i = 0; i < n; i++)
    {
        cout << a << " ";
        a++;
        if(a > 5) a = 1;
    }

    cout << endl << endl;

    return 0;
}
Ile wyrazów ciągu? : 12

1 2 3 4 5 1 2 3 4 5 1 2

2. Napisz program, który wyświetli zadaną liczbę wyrazów ciągu: 1 2 3 4 5 4 3 2 1 2 3 4 5 4 3 ...

Tutaj postępujemy podobnie, tworzymy pętlę wykonującą się n razy, określamy pierwszy wyraz ciągu i wyświetlamy go w pętli:

...

int i, n;              // Licznik i liczba obiegów

cin >> n;              // Odczytujemy liczbę obiegów

int a = 1;             // Pierwszy wyraz ciągu

for(i = 0; i < n; i++) // Pętla wykonuje się n razy
{
    cout << a << " ";
    ...
}

...

Co dalej? Zauważ, iż wyrazy ciągu najpierw rosną, a po osiągnięciu 5 maleją i po osiągnięciu 1 znów rosną, itd: Zatem do wyrazów musimy dodawać 1 lub odejmować 1 zależnie od cyklu, w którym znajduje się pętla:

1 → (+) → 5 → (-) → 1 → (+) → 5 →(-) → 1...

Odejmowanie jest równoważne dodawaniu liczby przeciwnej. Zatem do wyrazu ciągu raz dodajemy 1, a raz dodajemy (-1). Ponieważ dodawana wartość się zmienia, to umieścimy ją w zmiennej np. o nazwie d. Na początku wyrazy rosną, zatem d ma wartość 1. Zmienną tą dodajemy w pętli do wyrazu ciągu:

...

int i, n;              // Licznik i liczba obiegów

cin >> n;              // Odczytujemy liczbę obiegów

int a = 1;             // Pierwszy wyraz ciągu
int d = 1;             // Dodawany czynnik

for(i = 0; i < n; i++) // Pętla wykonuje się n razy
{
    cout << a << " ";
    ...
    a = a + d;         // Następny wyraz
}

...

Teraz musimy sprawdzić przed dodawaniem, jaką wartość ma wyraz ciągu a. Jeśli ma wartość 5, to następnym wyrazem musi być 4, zatem d ma przyjąć wartość -1. Jeśli ma wartość 1, to następnym wyrazem musi być 2, d ma przyjąć wartość 1:

...

int i, n;              // Licznik i liczba obiegów

cin >> n;              // Odczytujemy liczbę obiegów

int a = 1;             // Pierwszy wyraz ciągu
int d;                 // Dodawany czynnik

for(i = 0; i < n; i++) // Pętla wykonuje się n razy
{
    cout << a << " ";
    if(a == 1) d =  1;
    if(a == 5) d = -1;
    a = a + d;         // Następny wyraz
}

...

Program jest gotowy:

// Pętla iteracyjna
//-----------------

#include <iostream>

using namespace std;

int main()
{
    setlocale(LC_ALL,"");

    int i, n;

    cout << "Ile wyrazów? : "; cin >> n;

    int a = 1, d;

    for(i = 0; i < n; i++)
    {
        cout << a << " ";
        if(a == 1) d =  1;
        if(a == 5) d = -1;
        a = a + d;
    }

    cout << endl << endl;

    return 0;
}
Ile wyrazów? : 16
1 2 3 4 5 4 3 2 1 2 3 4 5 4 3 2

A tutaj masz nieco poprawiony program. Przeanalizuj dokładnie jego działanie:

// Pętla iteracyjna
//-----------------

#include <iostream>

using namespace std;

int main()
{
    setlocale(LC_ALL,"");

    int i, n;

    cout << "Ile wyrazów? : "; cin >> n;

    int a = 1, d = -1;

    for(i = 0; i < n; i++)
    {
        cout << a << " ";
        if((a == 1) || (a == 5)) d = -d;
        a = a + d;
    }

    cout << endl << endl;

    return 0;
}

Na podstawie tych programów rozwiąż podobne zadania z wyświetlaniem zadanej liczby wyrazów ciągów:

Do zapamiętania

Na początek:  podrozdziału   strony 

Pętle zagnieżdżone

Jeśli instrukcją  powtarzaną w pętli jest inna pętla, to powstają pętle zagnieżdżone (ang. nested loops). Tego typu konstrukcje pojawiają się w wielu algorytmach, dlatego należy je znać. Wykonajmy kilka prostych ćwiczeń. Napiszemy program, który wyświetla w wierszu zadaną liczbę literek X:
// Pętle zagnieżdżone
//-------------------

#include <iostream>

using namespace std;

int main()
{
    setlocale(LC_ALL,"");

    int i, n;

    cout << "n = "; cin >> n;
    cout << endl;

    for(i = 0; i < n; i++) cout << "X";
    cout << endl;

    cout << endl;

    return 0;
}
n = 10

XXXXXXXXXX

Dokonaj następujących zmian w programie:

Dopisz zmienną j w deklaracji zmiennych na początku programu:

int i, j, n;

Obejmij klamerkami pętlę for wraz z następującą za nią instrukcją wysyłającą manipulator endl do strumienia cout:

    {
        for(i = 0; i < n; i++) cout << "X";
        cout << endl;
    }

Zwróć uwagę, iż instrukcje wewnątrz klamerki są z odstępem 4 spacji.

Wstaw pusty wiersz przed klamerką otwierającą blok i w wierszu tym umieść instrukcję:

    for(j = 0; j < n; j++)
    {
        for(i = 0; i < n; i++) cout << "X";
        cout << endl;
    }

Otrzymaliśmy pętle zagnieżdżone. Zwróć uwagę, iż każda z tych pętli używa innej zmiennej licznikowej, aby się nawzajem nie zakłócały. Pętla zewnętrzna zlicza obiegi w zmiennej j, a pętla wewnętrzna robi to w zmiennej i.

Twój program powinien teraz wyglądać następująco:

// Pętle zagnieżdżone
//-------------------

#include <iostream>

using namespace std;

int main()
{
    setlocale(LC_ALL,"");

    int i,j,n;

    cout << "n = "; cin >> n;
    cout << endl;

    for(j = 0; j < n; j++)
    {
        for(i = 0; i < n; i++) cout << "X";
        cout << endl;
    }

    cout << endl;

    return 0;
}
n = 10

XXXXXXXXXX
XXXXXXXXXX
XXXXXXXXXX
XXXXXXXXXX
XXXXXXXXXX
XXXXXXXXXX
XXXXXXXXXX
XXXXXXXXXX
XXXXXXXXXX
XXXXXXXXXX

Pętla zewnętrzna wykonuje n obiegów. W każdym obiegu tej pętli wykonywane są instrukcje w bloku, a tam znajduje się pętla wewnętrzna wyświetlająca n literek X w wierszu oraz instrukcja kończąca ten wiersz (cout << endl;). Zatem program wyświetli n wierszy po n literek X w każdym i powstanie figura przypominająca prostokąt (faktycznie powinien to być kwadrat, ale literki są wyższe niż szersze).

Teraz spróbujemy ten program zmodyfikować na różne sposoby, aby otrzymywać figury ciekawsze od prostokąta. Zacznijmy od takiej figury:

n = 10

X
XX
XXX
XXXX
XXXXX
XXXXXX
XXXXXXX
XXXXXXXX
XXXXXXXXX
XXXXXXXXXX

Co tutaj się zmieniło? Poprzednio w każdym wierszu była wyświetlana stała ilość literek X, a teraz?

W wierszu pierwszym mamy 1 literkę X, w drugim 2, w trzecim 3, itd. Zatem liczba wyświetlanych literek w wierszu zależy od jego numeru. Pętla wewnętrzna musi uzależnić się od pętli zewnętrznej. Pętla wykonująca dokładnie n obiegów ma postać:

for(zmienna = 0; zmienna < n; zmienna++) ...

Jeśli n startuje od 0, to instrukcja ta przyjmie postać (dlaczego?):

for(zmienna = 0; zmienna <= n; zmienna++) ...

W naszym przypadku numer wiersza (startujący od 0) jest w zmiennej j, zatem program będzie teraz wyglądał następująco:

// Pętle zagnieżdżone
//-------------------

#include <iostream>

using namespace std;

int main()
{
    setlocale(LC_ALL,"");

    int i,j,n;

    cout << "n = "; cin >> n;
    cout << endl;

    for(j = 0; j < n; j++)
    {
        for(i = 0; i <= j; i++) cout << "X";
        cout << endl;
    }

    cout << endl;

    return 0;
}

Teraz twoja kolej. Przerób ten program tak, aby wyświetlał:

n = 10

XXXXXXXXXX
XXXXXXXXX
XXXXXXXX
XXXXXXX
XXXXXX
XXXXX
XXXX
XXX
XX
X

Następna figura:

n = 10

         X
        XX
       XXX
      XXXX
     XXXXX
    XXXXXX
   XXXXXXX
  XXXXXXXX
 XXXXXXXXX
XXXXXXXXXX

Zadanie to można rozwiązać na kilka sposobów, zastosujmy jednak taki, który pozwoli rysować dowolne figury.

Normalnie nie mamy w konsoli dostępu do każdego znaku w oknie, możemy jedynie wyświetlać wiersz za wierszem (dostęp taki jest możliwy, ale wymaga współpracy z Windows/Linux, co do prostych zadań nie należy). Na potrzeby zadań maturalnych to zupełnie wystarcza.

Wyobraźmy sobie, iż kolejne wiersze tworzą prostokątną mapę. Na mapie tej mamy współrzędne wierszową i kolumnową. Współrzędna wierszowa jest w zmiennej j (numer wiersza), a współrzędna kolumnowa jest w zmiennej i (numer znaku w wierszu):

Każdy kwadrat na tej mapie oznacza jeden znak w oknie konsoli. Szare prostokąty oznaczają znaki puste, czyli spacje. Zielone kwadraty oznaczają pozycje literek X tworzących naszą figurę. Jeśli n = 10, to mapa składa się z 10 wierszy o numerach od 0 do 9 po 10 znaków w każdym. Znaki mają numery od 0 do 9. Teraz każdy znak znajduje się na pozycji (j, i).

Strzałki pokazują przebieg tych numerów w trakcie wykonywania pętli w programie. Numery wierszy biegną z góry na dół, a numery znaków biegną od strony lewej do prawej. Czerwony prostokąt oznacza pozycję znaku, gdy j = 4, i = 5. Zwróć uwagę, że współrzędne zielonych kwadratów spełniają nierówność:

i n - j - 1

W pętli wewnętrznej musimy sprawdzać ten warunek przy pomocy instrukcji warunkowej if. Jeśli będzie spełniony, wyświetlamy literkę X, a jeśli nie, wyświetlamy spację. Zatem program wygląda następująco (w programie zamiast spacji wyświetlana jest kropka, aby pusty znak mapy był widoczny) :

// Pętle zagnieżdżone
//-------------------

#include <iostream>

using namespace std;

int main()
{
    setlocale(LC_ALL,"");

    int i,j,n;

    cout << "n = "; cin >> n;
    cout << endl;

    for(j = 0; j < n; j++)
    {
        for(i = 0; i < n; i++)
            if(i >= n - j - 1) cout << "X";
            else               cout << ".";
        cout << endl;
    }

    cout << endl;

    return 0;
}
n = 10

.........X
........XX
.......XXX
......XXXX
.....XXXXX
....XXXXXX
...XXXXXXX
..XXXXXXXX
.XXXXXXXXX
XXXXXXXXXX

Na koniec zaprojektujmy program rysujący kopertę:

n = 10

XXXXXXXXXX
XX......XX
X.X....X.X
X..X..X..X
X...XX...X
X...XX...X
X..X..X..X
X.X....X.X
XX......XX
XXXXXXXXXX

Stosujemy tę samą metodę mapy. Rysowanie rozpoczniemy od programu, który narysuje prostokąt z X, czyli zapełnioną mapę:

// Pętle zagnieżdżone
//-------------------

#include <iostream>

using namespace std;

int main()
{
    setlocale(LC_ALL,"");

    int i,j,n;

    cout << "n = "; cin >> n;
    cout << endl;

    for(j = 0; j < n; j++)
    {
        for(i = 0; i < n; i++)
            cout << "X";
        cout << endl;
    }

    cout << endl;

    return 0;
}
n = 10

XXXXXXXXXX
XXXXXXXXXX
XXXXXXXXXX
XXXXXXXXXX
XXXXXXXXXX
XXXXXXXXXX
XXXXXXXXXX
XXXXXXXXXX
XXXXXXXXXX
XXXXXXXXXX

W naszej kopercie występują tylko dwa puste wiersze: j = 0 i j = n - 1. Zatem w pętli wewnętrznej musimy sprawdzić, czy numer wiersza wynosi 0 lub n - 1. Jeśli tak, wyświetlamy X. Jeśli nie, wyświetlamy spację (tutaj kropkę dla przejrzystości):

// Pętle zagnieżdżone
//-------------------

#include <iostream>

using namespace std;

int main()
{
    setlocale(LC_ALL,"");

    int i,j,n;

    cout << "n = "; cin >> n;
    cout << endl;

    for(j = 0; j < n; j++)
    {
        for(i = 0; i < n; i++)
            if((j == 0) || (j == n - 1))
                 cout << "X";
            else cout << ".";
        cout << endl;
    }

    cout << endl;

    return 0;
}
n = 10

XXXXXXXXXX
..........
..........
..........
..........
..........
..........
..........
..........
XXXXXXXXXX

W instrukcji warunkowej if w pętli wewnętrznej zastosowaliśmy warunek (j == 0) || (j == n - 1). W warunku znajduje się operator alternatywy. Warunek jest prawdziwy, gdy jest prawdziwe jedno z wyrażeń (j == 0) lub (j == n - 1). Warunek ten jest prawdziwy tylko w wierszu pierwszym i ostatnim. Tam są zatem wyświetlane X. W pozostałych w pozostałych wierszach warunek jest fałszywy i program wyświetli tam znaki puste. Powstanie figura jak na powyższym obrazku.

To samo zastosujemy dla kolumn. W każdym wierszu w kolumnie 0 oraz n - 1 należy wyświetlić X, a w pozostałych kolumnach znak pusty. Dopisujemy zatem podobne wyrażenie dla zmiennej i:

// Pętle zagnieżdżone
//-------------------

#include <iostream>

using namespace std;

int main()
{
    setlocale(LC_ALL,"");

    int i,j,n;

    cout << "n = "; cin >> n;
    cout << endl;

    for(j = 0; j < n; j++)
    {
        for(i = 0; i < n; i++)
            if((j == 0) || (j == n - 1) ||
               (i == 0) || (i == n - 1))
                 cout << "X";
            else cout << ".";
        cout << endl;
    }

    cout << endl;

    return 0;
}
n = 10

XXXXXXXXXX
X........X
X........X
X........X
X........X
X........X
X........X
X........X
X........X
XXXXXXXXXX

Powstaje ramka. To cecha alternatywy logicznej. Jeśli jedno z podwyrażeń jest prawdziwe, to cały warunek jest również prawdziwy:

(j == 0) jest prawdziwe w całym wierszu 0, dlatego pojawiają się tam same X.
(j == n - 1) jest prawdziwe w całym wierszu ostatnim i jak wyżej.
(i == 0) jest prawdziwe w każdym wierszu w pierwszej kolumnie i pojawia się tam X.
(i == n - 1) jest prawdziwe w każdym wierszu w ostatniej kolumnie i jak wyżej.
Jeśli żaden z powyższych podwarunków nie jest prawdziwy, to cały warunek w if jest fałszywy i program w tym miejscu mapy wyświetla znak pusty.

W kopercie mamy jeszcze X na przekątnych. Zwróć uwagę, iż współrzędne pierwszej przekątnej spełniają warunek (i == j), a współrzędne drugiej przekątnej spełniają warunek (i == n - j - 1). Dopisujemy te warunki do alternatywy w instrukcji if i otrzymujemy kompletny program:

// Pętle zagnieżdżone
//-------------------

#include <iostream>

using namespace std;

int main()
{
    setlocale(LC_ALL,"");

    int i,j,n;

    cout << "n = "; cin >> n;
    cout << endl;

    for(j = 0; j < n; j++)
    {
        for(i = 0; i < n; i++)
            if((j == 0) || (j == n - 1) ||
               (i == 0) || (i == n - 1) ||
               (i == j) || (i == n - j - 1))
                 cout << "X";
            else cout << ".";
        cout << endl;
    }

    cout << endl;

    return 0;
}
n = 10

XXXXXXXXXX
XX......XX
X.X....X.X
X..X..X..X
X...XX...X
X...XX...X
X..X..X..X
X.X....X.X
XX......XX
XXXXXXXXXX

Zadania

Zaprojektuj programy, które rysują:

n = 10

X.X.X.X.X.X
.X.X.X.X.X.
X.X.X.X.X.X
.X.X.X.X.X.
X.X.X.X.X.X
.X.X.X.X.X.
X.X.X.X.X.X
.X.X.X.X.X.
X.X.X.X.X.X
.X.X.X.X.X.
    n = 10

XX..XX..XX
XX..XX..XX
..XX..XX..
..XX..XX..
XX..XX..XX
XX..XX..XX
..XX..XX..
..XX..XX..
XX..XX..XX
XX..XX..XX
    n = 10

XXXXXXXXXX
X........X
X.XXXXXX.X
X.XXXXXX.X
X.XXXXXX.X
X.XXXXXX.X
X.XXXXXX.X
X.XXXXXX.X
X........X
XXXXXXXXXX

Do zapamiętania

Na początek:  podrozdziału   strony 

Zespół Przedmiotowy
Chemii-Fizyki-Informatyki

w I Liceum Ogólnokształcącym
im. Kazimierza Brodzińskiego
w Tarnowie
ul. Piłsudskiego 4
©2023 mgr Jerzy Wałaszek

Materiały tylko do użytku dydaktycznego. Ich kopiowanie i powielanie jest dozwolone
pod warunkiem podania źródła oraz niepobierania za to pieniędzy.

Pytania proszę przesyłać na adres email: i-lo@eduinf.waw.pl

Serwis wykorzystuje pliki cookies. Jeśli nie chcesz ich otrzymywać, zablokuj je w swojej przeglądarce.

Informacje dodatkowe.