D/Instrukcje sterujące
Z Wikibooks, biblioteki wolnych podręczników.
Spis treści
|
[edytuj] Instrukcje sterujące
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).
[edytuj] Instrukcja warunkowa if
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.
[edytuj] Bloki i konwencja
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 z 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");
jednak zaciemnie to kod, i może to prowadzić do pewnych nieporozumień (o czym zachwilę).
Z kolei jeśli mamy kilka instrukcji do wykonania to grupujemy ją w blok przy pomocy klamr:
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.
[edytuj] Zagnieżdżanie
Instrukcje warunkowe można zagnieżdżać (ponieważ inst1 i inst2 może być dowolną instrukcją). Można wtedy tworzyć skomplikowane rozgałezienia 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");
}
[edytuj] Tworzenie łańcuchów warunków: else if
Czasami stosuje się taką konstrukcję w których w bloku else odrazu umieszcza się zagnieżdzożną 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)");
}
Konstukcja taka pozwala na łatwe sprawdzenie kolejnych warunków, wykonanie tylko jednej gałęzi i nie sprawdzanie kolejnych warunków.
[edytuj] Instrukcja wybór z pośród wielu możliwości: switch
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ę. 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);
}
[edytuj] Pętla dopóki: while
Pętle są chyba najważniejszym składnikiem programowania imperatywnego i służą temu co komputery robią najlepiej - powtarzaniu wielkrotnie podobnych instrukcji.
Załóżmy, że chcemy dodać liczby od 1 do 100. Następujący kod będzie marnotrastwem możliwości komputera:
int suma = 1 + 2 + 3 + ...... + 100; // gdzie kropki to ręcznie wpisane kolejne liczby.
Kod taki będzie tym trudny do napisania, trudny do rozszerzenia oraz zapewne zapomnimy jakiejś liczby.
Aby zrobić to poprawnie 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 <= 100) { // sprawdź czy zakończyć pętle
suma += i; // dodaj ten składnik
i++; // zwiększ składnik o 1 i przejdź do następnego cyklu
}
Po zakończeniu wykonywania tego fragmentu programu zmienna suma powinna zawierać wartość sumy, który możemy wkorzystać 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 drugkowania (tzw. leniwe debugowanie, bez używania dodatkowych narzędzi).
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 i suma. Ostateczny wynik zgadza się z znanym wzorem suma = n(n+1)/2, gdzie n to ostatni element sumy.
[edytuj] Pętla do..while
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);
-
- TODO: znalesc lepszy przykład**
[edytuj] Pętla for
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.
[edytuj] Operator przecinka
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ć: suma = 1 * 1 + 2 * 10 + 3 * 100 + 4 * 1000 + 5 * 104 + 6 * 105 + 7 * 106 + 8 * 107 = 1 + 20 + 300 + ... + 80000000 = 87654321
(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)
[edytuj] Pomijanie wyrażeń w for
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 śrdniku). W przypadku pustego init, nie dokonujemy poprostu ż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);
}
[edytuj] Pętla nieskończona
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 progamu 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 instrukcjie
// ...
}
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);
}
[edytuj] Błedy w pętlach
TODO: off-by-one errors
[edytuj] Przerywanie i kontynuacja pętli
break; continue;
[edytuj] Zagnieżdżanie pętli: przykłady
Proste zagnieżdżenie pętli:
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:
for (int i = 1; i < 10; i++) {
for (int j = 1; j < i; j++) {
suma += i*j;
}
}
Przerywanie pętli wewnętrznej:
for (int i = 1; i < 10; i++) {
int j = 1;
while (true) {
suma += i*j;
if (j > 10)
break;
}
}
Przerywanie pętli zewnęrznej:
pierwsza:
for (int i = 1; i < 10; i++) {
int j = 1;
while (true) {
suma += i*j;
if (j+i > 14)
break pierwsza;
}
}
Podobnie stosuje się etykiety przy continue. Etykiety są idetyfikują pewien punkt w programie. Tutaj pętlę for. Są one podobne jak instrukcje case w instrukcji wyboru switch, oraz jak etykiety w instrukcji skoku goto.
[edytuj] Instrukcja iterowania: foreach
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. 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ć najoptymalniejszą 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).
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)
{
writef("%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.
[edytuj] Instrukcja skoku: goto
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;
[edytuj] Ćwiczenia
[edytuj] Równanie kwadratowe (funkcje warunkowe i prosta matematyka)
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: ax2 + bx + c = 0.
Wskazówka: Dla tego równania (przy
) definiujemy wyróżnik: Δ = b2 − 4 * a * c
Wtedy rozwiązania są następujące:
Dla Δ = 0: jedno rozwiązanie 
Dla Δ > 0: dwa rozwiązania
oraz 
Dla Δ < 0: brak rozwiązań rzeczywistych.
Dla a = 0: 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
[edytuj] Sortowanie bąbelkowe
[edytuj] Quiz matematyczny (pętle)
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.