« poprzedni punkt  następny punkt »

3. Pętle iteracyjne o danej liczbie powtórzeń: instrukcja for

Instrukcja for ma następującą postać.


            for (init; wyr; upd) ins
gdzie:
  • część init -  może nie występować albo może być deklaracją jednej lub wielu zmiennych lub wyrażeniem lub listą wyrażeń rozdzielonych przecinkami.
  • część wyr - jest wyrażeniem o wartości typu boolean (lub może nie występować),
  • część upd  - jest wyrażeniem lub listą wyrażeń rozdzielonych przecinkami (lub może nie występować).
  • ins - jest instrukcją wykonywaną w pętli (w szczególności może to być instrukcja grupująca)
 

Instrukcja for tworzy pętlę, która działa w następujący sposób:

  1. Opracowywana jest deklaracja lub lista wyrażeń w części init  (jeśli występuje).
  2. Wyliczane jest wyrażenie wyr. Jeżeli jego wartość równa jest false, to instrukcja for kończy działanie, w przeciwnym razie (lub gdy wyr jest pominięte) działanie instrukcji for jest kontynuowane (krok 3).
  3. Wykonywana jest instrukcja ins (jeśli jest to instrukcja grupująca i w jej "środku" znajduje się instrukcja break lub return  przekazująca sterowanie poza blok tej instrukcji grupującej, to instrukcja for kończy działanie)
  4. Obliczane są  wyrażenia w części upd (jeśli występuje). Działanie jest wznawiane od kroku 2.

Porównując instrukcję for z instrukcją while można wskazać, że taki sam efekt jak za pomocą instrukcji:
                        for (init; wyr; upd) 
możemy uzyskać zapisując następujący fragment za pomocą instrukcji while:

init;
while (wyr) {
  ins;
  upd;
}

Dobry styl programowania nakazuje używać instrukcji for wyłącznie w tym celu.

Najprostsze zastosowanie ma instrukcja for przy organizacji pętli iteracyjnych ze znanym zakresem iteracji. W tym przypadku częśc init inicjuje licznik, wyrażenie wyr kontroluje granice licznika, część upd zmienia bieżącą wartość licznika.

Krótkie przykłady

int n = ...;
int sum = 0;
for (int i = 1; i <= n; i++) sum += i;

(sumuje n pierwszych dodatnich liczb całkowitych)

for (int i = 2; i <= n; i+=2) sum += i;

(sumuje n pierwszych dodatnich liczb parzystych)

for (int i = 1000; i  >= 990; i--) System.out.println(i);

(wyprowadza na konsolę kolejne liczby od 1000 do 990)

for (char c = 'a';  c <= 'z';  c++) System.out.print(c);

(wyprowadza na konsolę kolejne małe litery od a do z)

W części init może wystąpić deklaracja kilku zmiennych (ale jedna deklaracja, zatem muszą być to zmienne tego samego typu). Np.

for (int i =1, j = 2; i <=n && j <= m; i++, j++)  System.out.println(i+j);

Zasięg zmiennych deklarowanych w części inicjacyjnej  instrukcji for zaczyna się od miejsca deklaracji i kończy wraz z końcem instrukcji for

Obrazuje to poniższy rysunek:

Rys

Na przykład:
int a =0, n = 10;
for (int i = 0; i < n; i++) a += i;
for (int j = i; j < n*2; j++) a+=j; <--- błąd! zmienna i jest nieznana

System.out.println(i);  <--- błąd! zmienna i jest nieznana

for (int i = n; i< n*2; i++) a+=i;  <--- OK, nowa zmienna i

for (int i = 0; i < n; i++)  {         <--- OK, nowa zmienna i
      a += i;                                       używana w bloku
      System.out.println(i + " " + a)
}

for (int i = 0; i < n; i++)  {         <--- OK, nowa zmienna i
      int i = 1;                             <--- błąd!  redeklaracja zmiennej i
      ...
}

Terminem redeklaracja określamy ponowną deklarację zmiennej lokalnej w jej zasięgu, tzn. w bloku. Przypomnijmy: każda zmienne lokalna może być deklarowana tylko raz w bloku i nie jest dopuszczalne przesłanianie nazw zmiennych lokalnych w blokach wewnętrznych. Nazwy zmiennych, opisujących pola klasy mogą być w blokach metod przesłaniane.

Oczywiście, wcale nie musimy deklarować zmiennych w części inicjacyjnej instrukcji for, ale - pamiętajmy, że zgodnie z ogólną zasadą - każda zmienna musi być przed użyciem zadeklarowana:

int a =0, n = 10, i;
for (i=0; i < n; i ++) a+=i;        // Ok
for (i=n; i < n*2; i++) a+=i;      // Ok


Ale nie wolno pisać tak:

int i = 0;
for (int i = 0; i< 10; i++) ... //  błąd! redeklaracja i


Przejdżmy teraz do praktycznych zastosowań instrukcji for.
Pierwsze zadanie: za pomocą instrukcji for policzyć n-tą potęgę (n>=0) podanej liczby całkowitej a.


Przed lekturą dalszego tekstu, proszę samodzielnie wykonać to zadanie, definiując odpowiednią metodę w klasie Liczba, którą mieliśmy opracować w poprzednim podpunkcie.


Możliwe rozwiązanie - metoda pow w klasie Liczba, która może być użyta na rzecz dowolnej liczby całkowitej:

public class Liczba {

  int a;

  public Liczba(int liczba) {  // konstruktor
    a = liczba;
  }

//...

  public double pow(int n) {

    if (n < 0) {        // warunek konieczny: n >=0
       System.out.println("Niedopuszczalna wartość wykładnika");
       return -0.1;
    }
    double wynik = 1;
    for (int i = 1; i <= n; i++) wynik *= a;
    return wynik;
  }

Zauważmy, że w pętli for zastosowaliśmy zmiany licznika od 1 do n (włącznie). Ten sam efekt moglibyśmy osiągnąć pisząc: for (i=0; i<n; i++).

Instrukcje w następujących pętlach for zostaną wykonane n - razy

        for (int i = 1; i <= n; i++) ....

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

i pod względem liczby "obrotów" pętli obie instrukcje for są równoważne

Drugi praktyczny przykład zastosowania instrukcji for polega na rozwiązaniu następującego zadania.
W zbudowanej i omówionej wcześniej  klasie Account dostarczyć metody:
double getBalanceAfter(int n), która zwraca stan konta po n miesiącach.


Przed lekturą dalszego tekstu proszę spróbować samodzielnie rozwiązać to zadanie.


Możliwe rozwiązanie:

  public double getBalanceAfter(int n) {
     double wspOds = (interest/100)/12;
     for (int i = 1; i <= n; i++)
       balance += wspOds*balance +  monthIncome - monthExpend;
     return balance;
  }

Ogólnie, instrukcja for jest bardzo elastyczna. Spełniając reguły składniowe możemy pisać dosyć dziwne programy.
Np. poniższy program pyta o tekst, który ma okalać podawane następnie w pętli teksty i wyprowadza (w pętli) napisy z tym "ozdobnikiem" do chwili, gdy użytkownik nie wciśnie Cancel w dialogu wejściowym lub dopóki nie zostanie wyprowadzone LIMIT napisów.
Wszystko to realizowane jest w nawiasach pętli for (instrukcją wykonywaną w pętli jest instrukcja pusta - co oznaczamy pojedynczym średnikiem).

import javax.swing.*;

public class Napisy {

  final int LIMIT = 10;

  public static void main(String[] args) {
     new Napisy();
  }

  public Napisy() {
    String ozdoba = null,
           txt = null;
    int n = 0;

    for (ozdoba = ask("Co wokół?");
         n <= LIMIT && ozdoba != null && (txt = ask("Napis?")) != null;
         n++, System.out.println(n + ": " + ozdoba + txt + ozdoba) );

   System.exit(1);
  }

  private String ask(String txt) {
    return JOptionPane.showInputDialog(txt);
  }


}

Proszę skompilowac i wykonać ten program - tak by widzieć efekty jego działania.

Choć można tak programować, to jest to raczej niewskazane, gdyż program jest mało czytelny i narażony na błędy.
Podkreślić też warto, że części (init, wyr,  upd) instrukjci for są nieobowiązkowe (każda z nich może być pominięta). Przy tym jednak nie wolno pomijać średników rozdzielających te części.
W szczególności, za pomocą for w następujący sposób można zapisać odpowiednik pętli nieskończonej while(true):

for(;;) {
...
}

Oczywiście, wewnętrz takich nieskończonych pętli nalezy przewidzieć i umieścić warunki ich zakończenia za pomocą instrukcji break lub return.

Ogólnie jednak: używajmy instrukcji for wyłącznie do konstruowania pętli ze znaną liczbą lub zakresem iteracji, czyli w postaci:

        for ( i = ?; i <?; i = i ą ?) ...

wszystkie inne przypadki programując za pomoca instrukcji while lub do..while

Na koniec warto zwrócić uwagę na to, że pętle iteracyjne mogą być dowolnie zagnieżdżane.
Przecież ins w instrukcjach while, do..while i for - to dowolne instrukcje, w tym instrukcje sterujące.

Nic nie stoi na przeszkodzie, by np. zapisać taki fragment:

public class Nested {

  public static void main(String[] args) {
     String out = null;
     char c = 'a';
     while (c <= 'd') {
       for (int i=1; i<=2; i++) {
         out = "Dla " + c + " " + i + " mamy j =";
         for (int j = i; j <= i + 3; j++) out += " " + j;
         System.out.println(out);
       }
       c++;
     }
   }

}

i uzyskać wydruk:

Dla a 1 mamy j = 1 2 3 4
Dla a 2 mamy j = 2 3 4 5
Dla b 1 mamy j = 1 2 3 4
Dla b 2 mamy j = 2 3 4 5
Dla c 1 mamy j = 1 2 3 4
Dla c 2 mamy j = 2 3 4 5
Dla d 1 mamy j = 1 2 3 4
Dla d 2 mamy j = 2 3 4 5

Proszę przeanalizować wyniki i odpowiedzieć na pytanie: dlaczego sekwencje
litera nr
powtarzają się (tj dlaczego mamy a 1 i b 1, a 2 i b 2)
oraz dlaczego powtarzają się sekwencje j = 1 2 3 4 , j = 2 3 4 5


« poprzedni punkt  następny punkt »