D/Instrukcje sterujące

< D

Instrukcje sterujące edytuj

Instrukcje sterujące służą zmianie liniowego porządku wykonania. Możemy zażądać wykonania jednej z dwóch alternatywnych dróg (instrukcją warunkową), czy też wykonania danego fragmentu wielokrotnie (pętlami).

Instrukcja warunkowa if edytuj

Instrukcja if (z ang. jeżeli), służy wykonaniu lub ominięci pewnej parti programu przy określonych warunkach.

Składnia:

if (warunek)                      // jeśli (warunek jest prawdziwy)
   inst1;                         //    to wykonaj inst1;

Przy czym warunek musi być wyrażeniem typu bool (może to być np. porównanie, wykonanie jakiejś funkcji zwracającej bool, czy też wynik operacji logicznych przedstawionych w rozdziale o zmiennych logicznych).

Instrukcja inst1 to z kolei dowolna pojedyncza instrukcja, lub blok (który jak wiemy zachowuje się jak jedna instrukcja).

Instrukcja inst1 jest wykonywana jedynie jeśli warunek da w wyniku true.

Przykład:

int a = 15;

writefln("a wynosi ", a);

if (a < 10)                                  // 1. warunek
   writefln("a jest mniejsze niż 10");

if (a < 100)                                 // 2. warunek
   writefln("a jest mniejsze niż 100");

if ((a == 42) || (a == 15))                  // 3. warunek
   writefln("a jest jedna z odpowiedzi");

Fragment ten da w wyniku:

a wynosi 15
a jest mniejsze niż 100
a jest jedna z odpowiedzi

ponieważ pierwszy warunek jest fałszywy: a < 10, czyli 15 < 10, jest fałszem (false); a drugi warunek jest prawdziwy (15 jest mniejsze 100); trzeci warunek też jest prawdziwy (a jest równe 42 lub 15, w tym przypadku 15).

Jak widać w zależności jaka jest wartość zmiennej a, przed instrukcjami warunkowymi, wykonują się różne instrukcje. Dzięki temu możemy obsłużyć różne sytuacje w różny sposób.

Pomocne jest również słowo kluczowe else (ang. inaczej, w przeciwnym razie)

if (warunek)                              // jeśli (warunek jest prawdziwy)
   ints1;                                 //    wykonaj inst1;
else                                      // w przeciwnym wypadku
   inst2;                                 //    wykonaj inst2;

Instrukcja inst1 wykona się jeśli warunek jest prawdziwy, natomiast inst2 jeśli warunek jest nie prawdziwy. Tylko jedna z dwóch instrukcji się wykona.

Na przykład.

int b = 61;

if (b < 50)
   writefln("b jest mniejsze od 50");
else
   writefln("b nie jest mniejsze od 50");

da w wyniku:

b nie jest mniejsze od 50

Warto przypomnieć tutaj o białych znakach, i zaznaczyć, że format tych instrukcji, którą tutaj zaprezentowano jest jedną z wielu, ale również jedną z bardziej przejrzystych. Można napisać:

if (b < 5)
 writefln("a mniejsze niz 5");

czy

if(b < 5)
 writefln("a mniejsze niz 5");

czy

if (b < 5) writefln("a mniejsze niz 5");

czy

  if (b < 5)
writefln("a mniejsze niz 5");

czy

if (
  b < 
         5)
 writefln(
   "a mniejsze niz 5")
 ;

są dokładnie tym samym (choć nie nie zawsze jasne do przeczytania). Zwykle będziemy stosować przejrzystą formę pokazaną, na początku rozdziału, i to samo się czyni innych instrukcji sterujących podanych w dalszej części.

Bloki i konwencja edytuj

Jak już wspomniano instrukcja inst1 (i inst2 również), może być blokiem instrukcji, np.

int a = 5;
int b;
if (a == 5 || a == 7) {
   writefln("a jest równe:");
   writefln("pięć");
   writefln("albo");
   writefln("siedem");
   b = 2*a;
}

i podobnie z else:

int a = 555;
if (a < 10)
   writefln("a jest małe");
else {
   writefln("a jest duże");
   writefln("zapewne bardzo duże");
}

czy też:

int a = 5, b = 5;
if (a == b) {
   writefln("a jest równe b");
   writefln("a wiec ich rożnica to 0");
} else {
   writefln("a jest różne od b");
   writefln("a wiec ich różnica nie jest 0");
}

Jak widać kiedy za instrukcją warunkową jest tylko jedna instrukcja, nie ma potrzeby tworzyć bloku:

bool test = true;
if (test)
   writefln("prawda");

co czasami wygodnie zapisać w jednej linijce:

bool test = true;
if (test) writefln("prawda");

podobnie możemy skrócić if-else do dwóch linijek (zamiast ok. 4):

bool test = false;
if (test) writfln("prawda");
else writefln("fałsz");

czy nawet jednej

bool test = false;
if (test) writfln("prawda"); else writefln("fałsz");

jednak zaciemnie to kod, i może to prowadzić do pewnych nieporozumień (o czym za chwilę).

Z kolei jeśli mamy kilka instrukcji do wykonania to grupujemy ją w blok przy pomocy nawiasów klamrowych:

if (test) {
  writefln("prawda");
  writefln("test jest prawdą");
}

a nie:

if (test)
   writefln("prawda");
   writefln("inny napis");       // UWAGA: to jest poza if
                                 // wykona sie nie zaleznie od tego jakie jest test

ponieważ druga instrukcja writefln nie jest związana z instrukcją if - jest to instrukcja poza nią i najlepiej zapisać, ten fakt wyraźnie, używając wcięć:

if (test)
   writefln("prawda");
writefln("inny napis");                   // poza if

W niniejszym podręczniku, będziemy używać klamr i jawnego bloku w zasadzie zawsze, nawet dla jednej instrukcji:

if (test) {
   writefln("prawda");
}

ponieważ, łatwo potem możemy dopisać nowe instrukcje, oraz wskazuje na "koniec" instrukcji warunkowej, nawiasem zamykającym.

Zagnieżdżanie edytuj

Instrukcje warunkowe można zagnieżdżać (ponieważ inst1 i inst2 może być dowolną instrukcją). Można wtedy tworzyć skomplikowane rozgałęzienia w programie. Na przykład:

if (x < 10) {
   if (y < x) {
      writefln("y mniejsze niż x, a x mniejsze niż 10");
   } else {
      writefln("y większe niż x, a x mniejsze niż 10");
   }
} else {
   writefln("x wieksze od 10");
}

Tworzenie łańcuchów warunków: else if edytuj

Czasami stosuje się taką konstrukcję w których w bloku else od razu umieszcza się zagnieżdżoną instrukcję warunkową if, w której bloku else można umieścić kolejne instrukcje. Załóżmy że x jest liczbą nie ujemną. Tworzy konstrukcje która wydrukuje opis tej liczb. Ciąg w rodzaju:

if (x == 0) {
   writefln("zero");
} else if (x == 1) {
   writefln("jeden");
} else if (x == 2) {
   writefln("dwa");
} else if (x < 10) {
   writefln("kilka");
} else if (x < 20) {
    writefln("kilkanaście");
} else if (x < 100) {
   writefln("kilkadziesiąt");
} else {
   writefln("dużo (powyżej sto)");
}

Konstrukcja taka pozwala na łatwe sprawdzenie kolejnych warunków, wykonanie tylko jednej gałęzi i nie sprawdzanie kolejnych warunków.

Instrukcja wybór z pośród wielu możliwości: switch edytuj

W przypadku kiedy łańcuch else if zawiera testy równości pojedyńczej zmiennej, można wykorzystać insturkcję switch.

switch (x) {
  case 0:
    writefln("zero"); break;
  case 1:
    writefln("jeden"); break;
  case 2,3,4,5,6,7,8,9:
    writefln("kilka"); break;
  default:
    writefln("dużo (dziesięć lub więcej) lub liczba ujemna"); break;
}

Instrukcję case oznaczją etykiety do których trzeba przejść w wypadku zachodzenia odpowiedniej równości. Wartości za case muszą być stałe. Etykiety case są sprawdzane od góry (a przynajmniej tak powinno się wydawać programiście). W przypadku kiedy jedna z wartości przy case jest równa zmiennej x, program wykonuje wszystkie instrukcje za tą etykietą.

Instrukcja break (ang. przerwij, złam) jest tutaj wymagana ponieważ np. w wypadku gdyby ich nie było a zmienne x równała by się 1, to otrzymalibyśmy nastepujący wynik:

jeden
kilka
dużo (dziesięć lub więcej) lub liczba ujemna

Czasami pominięcie instrukcji break jest potrzebne, ale są to specyficzne sytuację (tzw. fall-through, przejście, przeskok, spadek). Inne użycie instrukcji break znajdziemy w instrukcjach pętli.

Etykieta default pasuje do wszystkich wartości zmiennej x (analogicznie ostatnie else, w poprzednim paragrafie, jeśli w każdym case jest brake). Może więc służyć do obsługi nie standardowych sytuacji. Instrukcja break w default nie jest potrzebny, ponieważ i tak jest to ostatnia instrukcja. W przypadku kompilowania z włączonymi ostrzeżeniami etykieta default jest wymagana (można dodać tam instrukcję assert(0), jeśli jesteśmy pewni że program nigdy nie wykona tego bloku).

Oprócz liczb można również sprawdzać łańcuchy znaków, np.

switch (opcja) {
  case "-vv":
    debug = true; // bez break, bo -vv implikuje -v
  case "-v":
    verbose = true; break;
  case "-h":
    print_help(); break;
  default:
    writefln("Nieznana opcja"); exit(1); assert(0);
}

Pętla dopóki: while edytuj

Pętle są chyba najważniejszym składnikiem programowania imperatywnego i służą temu co komputery robią najlepiej - powtarzaniu wielokrotnie podobnych instrukcji.

Załóżmy, że chcemy dodać liczby od 1 do 10. Następujący kod będzie marnotrawstwem możliwości komputera:

 int suma = 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10;

Kod taki będzie tym trudny do napisania, trudny do rozszerzenia oraz zapewne zapomnimy jakiejś liczby, np. jeśli zechcemy policzyć sumę liczb od 1 do 100.

Aby zrobić to sensowniej użyjemy pętli while (ang. dopóki):

while (test)
  instr;

Instrukcja instr jest wykonywana wielokrotnie dopóki test jest prawdą. Jeśli test jest fałszem instrukcja kończy swoje wykonywanie. Warunek jest sprawdzany tylko przed każdym wykonaniem instrukcji instr.

Do naszego celu użyjemy w takim razie następującej konstrukcji:

int suma = 0;     // początkowa wartość sumy
int i = 1;        // obecnie dodawany składnik
while (i <= 10) { // sprawdź czy zakończyć pętle
  suma += i;       // dodaj ten składnik, pamiętaj że jest to równoważne suma = suma + i;
  i++;             // zwiększ składnik o 1 i przejdź do następnego cyklu, tzn. i = i + 1;
}

Po zakończeniu wykonywania tego fragmentu programu zmienna suma powinna zawierać wartość sumy, który możemy wykorzystać gdzieś dalej (na przykład wyświetlić). Sprawdź czy program poprawnie oblicza sumę liczb od 1 do 10, zmieniając warunek while oraz drukując wynik.

Aby lepiej zrozumieć działanie tego programu, prześledźmy pętlę krok po kroku, wstawiając dodatkowe instrukcje drukowania:

import std.stdio;
void main() {
  int suma = 0;     // początkowa wartość sumy
  int i = 1;        // obecnie dodawany składnik
  while (i <= 10) { // sprawdź czy zakończyć pętle
    writefln("Aktualny składnik: ", i);
    suma += i;      // dodaj ten składnik
    writefln("Aktualna podsuma: ", suma);
    i++;            // zwiększ składnik o 1 dla następnego obiegu
  }
  writefln("Suma to: ", suma);
}

Program wyświetli:

Aktualny składnik: 1
Aktualna podsuma: 1
Aktualny składnik: 2
Aktualna podsuma: 3
Aktualny składnik: 3
Aktualna podsuma: 6
Aktualny składnik: 4
Aktualna podsuma: 10
Aktualny składnik: 5
Aktualna podsuma: 15
Aktualny składnik: 6
Aktualna podsuma: 21
Aktualny składnik: 7
Aktualna podsuma: 28
Aktualny składnik: 8
Aktualna podsuma: 36
Aktualny składnik: 9
Aktualna podsuma: 45
Aktualny składnik: 10
Aktualna podsuma: 55
Suma to: 55

Pokazując w jaki sposób kolejno zmienia się wartość zmiennej i oraz suma. Ostateczny wynik zgadza się z znanym wzorem na sumę elementów szeregu arytmetycznego 1, 2, ... n, to jest suma = n*(n+1)/2.

Pętla do..while edytuj

Istnieje drugi typ pętli do, zwany do-while. W przeciwieństwie do zwykłej pętli warunek jej powtarzania sprawdza się na końcu.

do
  instr
while (test);

Najpierw zostanie wykonana instrukcja instr, a następnie sprawdzony warunek test. Jeśli test będzie prawdą to pętla będzie powtarzana. Zauważ, że instrukcja instr będzie wykonana conajmniej raz.

Nasz przykład z sumowaniem możemy zapisać przy pomocy tej pętli:

int suma = 0;
int i = 1;
do {
  suma += i;
  i++;
} while (i <= 10);

Wygląda bardzo podobnie. Aby zobaczyć różnice, zaużmy że chcemy wykonać sumę samych siódemek, dopóki nowa suma będzie mniejsza niż 100. Przy pomocy pętli do wykonamy to poprawnie tak:

int suma = 0;
while (suma+7 < 100) {  // sprawdzamy czy nowa suma będzie mniejsza od 100
  suma += 7;            // jeśli tak to dodajemy te 7
}

Suma ta powinna się wynieść 98, ponieważ suma 14*7=98, a już 15*7=105, tak więc dopiero po piętnastym przebiegu pętli nasz warunek stanie się fałszywy.

W przypadku pętli do..while, i przepisaniu naiwnie tego kodu do postaci:

int suma = 0;
do {
  suma += 7;
} while (suma+7 < 100);

Pętla for edytuj

Pętla for jest chyba najważniejsza pętlą, ponieważ używa się jej najłatwiej, oraz służy bardzo często do operowania na tablicach i ciągach liczb.

for (init; test; incr)
  instr;

Należy to rozumieć w nastepujący sposób:

1. Wykonaj wyrażenie init (od ang. inicjalizacja)
2. Sprawdź czy wyrażenie test jest prawdziwe, jeśli nie to zakończ pętle
3. Wykonaj instrukcje instr
4. Wykonaj wyrażenie incr (o ang. increment, zwiększ, krok)
5. Przejdź do punktu 2

Łatwo można to przepisać do postaci pętli do (jedyna różnia jest w zachowaniu instrukcji continue, o której później):

{
  init;
  do (test) {
    instr;
    incr;
  }
}

Poprzedni przykład z sumowaniem liczby przy pomocy pętli do, możemy zapisać w następujący sposób:

int suma = 0;
for (int i = 1; i <= 10; i++) {
   suma += i;
}

Co można przetłumaczyć na polski jako: "Dla i od 1 do 10 (włącznie) dodawaj i do suma, oraz zwiększaj i o 1 w każdym kroku"

Wykorzystaliśmy tutaj dodatkowo fakt że w init można zadeklarować zmienne która jest widoczna (tzn. w tym samym zasięgu) zarówno w częsci test, incr, jak i instr, ale nie za for (tzn. definiujemy prywatną zmienną do użytku w pętli). W przypadku jeśli zmienna którą definujemy (tutaj i) już była użyta w funkcji w której for się znajduje kompilator zgłosi nam błąd.

Operator przecinka edytuj

Czasami w części init chcemy zainicjalizować kilka rzeczy (np. definicje zmiennych lokalnych). Ponieważ nie możemy tutaj użyć bloku (blok jest instrukcją, a nie wyrażeniem), możemy wykonać inicjalizację zaraz przed instrukcją for. Niestety powoduje to że definiowana zmienna jest potem widoczna w dalszej częsci funkcji, a przecież chcieliśmy aby był lokalna. Możemy tutaj użyć operatora przecinka:

expr1, expr2

Operator ten powoduje, że najpierw wykonuje się wyrażenie expr1, a potem wyrażenie expr2, a wartością całego wyrażenia (expr1, expr2) jest wartośc wyrażenia expr2. Można użyć więcej przecinków, ponieważ operator , jest lewostronnie łaczny, tzn.

epxr1, expr2, expr3  == (expr1, expr2), expr3

Tak więc wykonają się wyrażenia od lewej do prawej, a wartościa całości będzie ostatni po prawej (no chyba że po drodze stanie się coś wyjątkowego).

Co to ma do pętli for i wielokrotnej inicjalizacj? Zobaczmy na przykładzie:

int suma = 0;
for (int i = 1, int j = 1; i <= 8; i++, j*=10) {
  suma += i*j;
}

Definujemy tutaj dwie zmienne typu int (i, j), rozpoczynając od 1, i obliczamy sumę iloczynu tych liczb, w następnym kroku i zwiększamy o 1, a j zwiększamy 10 razy (jak widać w wyrażeniu incr też użyliśmy operatora przecinka do wykonania dwóch zmian).

Wartośc sumy powinna wynosić:  

(Zmiejszyliśmy tutaj warunek test, aby tylko zrobić 8 przebiegów pętli, bo jak widać mnożenie przez 10 powoduje nam tworzenie dużych liczb, i następna liczba mogła by się nie zmieścić w typie int)

Pomijanie wyrażeń w for edytuj

Dodatkową ciekawą własnością instrukcji for jest to, że każde z wyrażeń init, test, incr może być puste (należy jednak pamiętać o średniku). W przypadku pustego init, nie dokonujemy po prostu żadnej inicjalizacji (np. nie potrzebujemy żadnej nowej zmiennej, albo zainicjowaliśmy je przed for). W przypadku pustego incr nie zmieniamy niczego po każdym obiegu pętli (poza tym co wykonuje instrukcja instr, i ewentualnie test). W przypadku pustego test jest on zastępowany wartością true (co powoduje, że pętla wykonuje się w nieskończoności jeśli nie zatrzymamy jej w inny sposób).

Zobaczmy na przykłady.

int i = 0;
...
i = 0;
for (; i <= 10; i++) {
  writefln(i);
}

Czasami możemy chcieć przenieść wyrażenie incr bezpośrednio do instrukcji instr (na jej koniec), na przykład kiedy jest ona skomplikowana, albo zależy w skomplikowany sposób od instrukcji instr:

for (int i = 0; i <= 10; ) {
  writefln(i);
  i++;
}

Również czasami warunek test może być trudny do opisania i możemy zapisać go w instrukcji instr:

for (int i = 0; ; i++) {
  if (i > 10) break;         // użyliśmy tutaj instrukcji break,
                             // która przerywa wykonywanie pętli
  writefln(i);
}


Pętla nieskończona edytuj

Często spotykanym sposobem na wykonanie pętli nieskończonej (tzn. takiej która w normalnym przypadku nie kończy się), jest użycie właśnie pętli for z pominiętymi wszystkimi trzema członami. Na przykład:

for (;;) {
  writefln("Cześć");
}

Po włączeniu takiego programu program będzie wyświetlał wielokrotnie napis Cześć. Niestety w normalnym wypadku nie będziemy mogli przerwać jego wykonania, ponieważ w programie nie ma na to żadnego warunku (mówimy że program się zawiesił, albo zapętlił). W większości systemów operacyjnych możemy posłużyć się skrótem klawiszowym Ctrl-C lub skorzystać z programu do obsługi procesów.

Konstrukcja taka może tyć pomocne np kiedy w pętli obsługujemy interakcję z klawiaturą/użytkownikiem i nie wiemy, ile razy będziemy musieli obsłużyć te dane. Dopiero np. odebranie informacji o naciśnięciu klawisze Esc czy Q spowoduje, że dodatkowy warunek w pętli for ją przerwie (np. przy pomocy brake o którym za chwilę, albo return).

for (;;) {
  writefln("Podaj komende");

  int a = odczytaj_klawisz();

  if (a == 'Q' || a == 'q') {
     return;
  }

  // ...
  // normalne przetwarzanie instrukcje
  // ...
}

Konstrukcja taka również może być pomocna jeśli chcemy np. wykonywać w nieskończoność jakieś okresowe czynności (np. zbieranie statystyk o systemie), wtedy zwykle w pętli włączymy jakies opóźnienie.

for (;;) {
  zbierz_i_zapisz_statystyki();
  sleep(3600);   // sleep (and. śpij) - czekaj 3600 sekund, tj. 1 godzinę
}


Jeszcze inną sytuacją jest serwer sieciowy który czeka na połączenia od klientów którzy mogą w nieskończoność coś od niego żądać - wtedy pętla for będie realizowała czekanie na kolejne zgłoszenie.

for (;;) {
  polacznie = czekaj_na_polacznie();
  zgloszenie = odczytaj_zgloszenie(polaczenie);
  odpowiedz = wykonaj(zgloszenie);
  wyslij(polaczenie, odpowiedz);
}

Błędy w pętlach edytuj

Jednym z najczęstszych błędów popełnianych przy pętlach jest tzw. off-by-one, przeanalizujmy poniższy kod:

int tab[100];
for(int i = 0; i <= 100; i++)
 tab[i] = i * i;

Deklarujemy 100–elementową tablicę i, zdawałoby się, wypełniamy kolejne komórki kwadratami ich indeksów, problem polega jednak na tym, że pętla nie wykonuje się 100 a 101 razy. Tak jak for(int i = 0; i <= 2; i++) wykona się 3 razy, dla i równego kolejno 0, 1 i 2.

Przerywanie i kontynuacja pętli edytuj

Czasami warunek zakończenia pętli jest zbyt skomplikowany, aby można było go czytelnie umieścić jako warunek pętli. Dzieje się tak w szczególności, kiedy chcemy przerwać pętlę wcześniej niż początkowo zakładaliśmy. Można to rozwiązać przy pomocy słów kluczowych break (z ang. przerwij) i continue (z ang. kontynuuj).

int x = 91873817;
for (int i = 2; i < x; i++) {
  if ( (x % i) == 0 ) { // sprawdź czy i dzieli x, bez reszty
    writefln("Liczba %d jest złożona, bo jest podzielna przez %d", x, i);
    break; // nie ma sensu sprawdzać dalej, wiemy że x jest złożona
           // przerwij wykonywanie pętli, nawet jeśli i jest nadal mniejsze od x
  }
}


int iloczyn = 1, ilosc = 0, suma = 0;
for (int i = 2; i < 20; i++) {
  if (!jest_pierwsza(i)) {
    continue; // zignoruj jeśli liczba jest złożona
              // wykonanie continue, natychmiast rozpocznie następny obieg pętli
              // tzn. zamiast wykonywać kod poniżej, wykona i++, i wykona pętlę jeszcze raz
  }
  ilosc += 1;     // ten kod się nie wykona, jeśli powyższe continue zostało wykonane w bieżącym obiegu
  iloczyn *= i;
  suma += i;
}
writefln("Ilosc liczb pierwszych ponizej 20: %d", ilosc);
writefln("Iloczyn liczb pierwszych ponizej 20: %d", iloczyn);
writefln("Suma liczb pierwszych ponizej 20: %d", suma);

Czasami jest to wygodniejsze, niż użycie if/else, zwłaszcze, jeśli mamy głęboko zagnieżdżone instrukcje.

Zagnieżdżanie pętli: przykłady edytuj

Nic nie stoi na przeszkodzie, aby wewnątrz jednej pętli użyć kolejnej pętli. Poniższe przykłady pokazują kilka ciekawych zastosowań.

Proste zagnieżdżenie pętli:

int suma = 0;
for (int i = 1; i < 10; i++) {
  for (int j = 1; j < 10; j++) {
      suma += i*j;
  }
}

Zależność warunku wewnętrznej pętli od zewnętrznej:

int suma = 0;
for (int i = 1; i < 10; i++) {
  for (int j = 1; j < i; j++) {
      suma += i*j;
  }
}

Przerywanie pętli wewnętrznej:

int suma = 0;
for (int i = 1; i < 10; i++) {
  int j = 1;
  while (true) {
     suma += i*j;      
     if (j > 10) {
        break;
     }
  }
}

Przerywanie pętli wewnętrznej oraz zewnętrznej:

   int suma = 0;
 pierwsza:                              // etykieta
   for (int i = 1; i < 10; i++) {
     int j = 1;
     while (true) {
       suma += i*j;      
       if (j+i > 14)
          break pierwsza;               // przerwij pętlę for (a tym samym również pętlę while).
     }
   }

Podobnie stosuje się etykiety przy continue. Etykiety identyfikują punkt w programie/module. Tutaj etykieta pierwsza wskazuje na pętlę for. Są one podobne jak instrukcje case w instrukcji wyboru switch, oraz jak etykiety w instrukcji skoku goto.

Instrukcja iterowania: foreach edytuj

D posiada bardzo wygodną konstrukcję foreach (ang. od for each, dla każdego), która pozwala na łatwe operowanie na tablicach różnego typu, jak również na operowanie na własnych typach. Konstrukcja foreach pozwala przerzucić na kompilator troskę o konkretną implementację pętli (może przetransformować ją np. do instrukcji for, albo while, i używać ukrytych indeksów, albo np. wskaźników do danych), tak aby wybrać najszybszą wersję oraz zmniejszyć ryzyko pomyłki (na przykład pomylenie warunków końca, początku, tzw. "of by one errors", ang. błędy przesunięcia o jeden). Poza tym jest zwykle krótsza, niż ręczne operowanie indeksami i pętlą for.

Na przykład jeśli chcemy dodać liczby z tablicy, wykonamy następujący kod:

int[] tablica = [1, 5, 10, 33];
int suma = 0;
foreach(x; tablica) {
   suma += x;
}

Instrukcja iterowania może być też wykonywana w przeciwnym kierunku:

int[] tablica = [1, 5, 10, 33];
foreach_reverse(x; tablica) {
  writefln("%d", x);
}

W tym wypadku pętla wypisze liczby w odwrotnej kolejności.

Więcej o tej instrukcji w rozdziale o tablicach zwykłych, asocjacyjnych oraz przeładowaniu operatorów w programowaniu obiektowym. W szczególności, własne typy można rozbudować tak aby miały sensowną operację iterowania, która będzie można używać w konstrukcji foreach.

Instrukcja skoku: goto edytuj

Użycie tej instrukcji jest nie wskazane. Już w roku 1965 N. Writh przestrzegał przed używaniem tej instrukcji, ponieważ trudno się czyta program napisany z ich użyciem. Zanim wymyślono języki strukturalne (zawierające pętle while, czy procedury) większośc kodu wykorzystywała goto do powtarzania różnych instrukcji. Był to substytut pętli. N. Writh uznając ta instrukcję za nieporządaną stworzył język Pascal, powszechnie potem używany w edukacji informatyków.

Niektóre języki (np. Java, mimo podobieństwa do C) nie posiadaja tej instrukcji. W D instrukcja ta jest dostępna, ponieważ czasami, w bardzo dziwnych przypadkach (np. obsługa sytuacji awaryjnych) jest ona najprostszym rozwiązaniem.


etykieta:
...  //jakieś instrukcje
goto etykieta;

Ćwiczenia edytuj

Równanie kwadratowe (funkcje warunkowe i prosta matematyka) edytuj

Napisz program który przyjmuje trzy dodatkowe argumenty liczbowe, a b c, i znajduje pierwiastki równania kwadratowego i wypisuje odpowiednie komunikaty. Równanie kwadartowe:  .

Wskazówka: Dla tego równania (przy  ) definiujemy wyróżnik:  

Wtedy rozwiązania są następujące:

Dla  : jedno rozwiązanie  

Dla  : dwa rozwiązania   oraz  

Dla  : brak rozwiązań rzeczywistych.

Dla  : program powinien zgłosić błąd i ewentualnie rozwiązać równanie liniowe (x_3 = -c/b), o ile b jest różne od zera.

Wskazówki: funkcje pierwiastka to sqrt z modułu std.math, argumenty programu znajdziesz w tablicy args na pozycjach 1,2,3 (np. args[1] to a), a nastepnie skonwertuj je na liczby przy pomocy funkcji atof z modułu std.string. Wyniki wypisz z odpowiednią ilością cyfr znaczących, ale nie więcej niż pięcioma - użyj odpowiedniego formatu i jego parametrów.

Dodatkowo: zmodyfikuj swój program aby obsługiwał rozwiązania zespolone, a potem również współczynniki zespolone (użyj typu cdouble, i pozwól programowi przyjmować 6 argumentów, pierwiastek z liczby ujemnej oblicz wspomagając się jakaś książką z matematyki). Zgłaszaj błędy w wypadku błędnych parametrów (np. podania znaku a nie liczby) albo ich złej ilości (args.length zawiera ilość argumentów, nazwa programu to też argument).

Oczekiwane wyniki:

$ ./kwadratowe 1 0 0
Dwa rozwiązania: 0 0
$ ./kwadratowe 0 1 3
To nie jest równanie kwadratowe
$ ./kwadratowe 1 0 -4
Dwa rozwiązania: -2 2
$ ./kwadratowe 1 0 4
Brak rozwiązań rzeczywistych
$ ./kwadratowe 1 -6 9
Jedno rozwiązanie: 3
$ ./kwadratowe 1 0 -2
Dwa rozwiązania: -1.41421 1.41421

Sortowanie bąbelkowe edytuj

Quiz matematyczny (pętle) edytuj

Losowanie dwóch liczb całkowitych i jednego z czterech podstawowych działań, i pytanie użytkownika o prawidłowy wynik, powtarzanie 3-krotnie próby, kończenie gry po 20 pytaniach, i zwiększanie trudności (losowanie większych liczb). Użyj funkcji std.random.random() do generowania liczb losowych, switch do wybierania działania, kilku zmiennych do przechowywania stanu gry, oraz pętli while do powtarzania pytań i zadawania kolejnych.