This is an old revision of the document!
Laboratorium: Pętla for, pętla "while", debuggowanie
/* Program oblicza sume liczb naturalnych 1,2,...,10. */ #include <stdio.h> #include <stdlib.h> int main() { int i = 0; int suma = 0; for (i = 1; i <= 10; i = i + 1) { suma = suma + i; } printf("suma = %d\n", suma); return 0; }
Zwróć uwagę, że równie dobrze można zastosować następującą pętlę for
:
for (i = 0; i < 11; i = i + 1) { suma = suma + i; }
gdyż dodanie $0$ do zmiennej suma
nie zmieni jej wartości, a warunki $i \leq 10$ oraz $i < 11$ są równoważne
ALE
pierwszy sposób ($i_0 = 0$, $i \leq 10$) lepiej przekazuje intencję programisty, przez co kod jest bardziej zrozumiały!
/* Program oblicza sume liczb calkowitych z przedzialu <a,b>. */ #include <stdio.h> #include <stdlib.h> int main() { int a = 0; int b = 0; int suma = 0; int i; printf("Podaj liczby `a` i `b`: "); scanf("%d %d", &a, &b); for (i = a; i <= b; i = i + 1) { suma = suma + i; } printf("suma = %d\n", suma); return 0; }
W powyższym przykładzie zmienna i
oznacza aktualnie sumowaną liczbę.
Zwróć uwagę, że równie dobrze można zastosować następującą pętlę for
:
for (i = 0; i <= b - a; i = i + 1) { suma = suma + (i + a); }
w którym zmienna i
oznacza indeks aktualnie sumowanej liczby – bądź inaczej mówiąc odległość aktualnie sumowanej liczby od lewego krańca przedziału (czyli od $a$).
Podobnie jak w przypadku zadania FOR-1, pierwszy sposób lepiej przekazuje intencję programisty, jest prostszy do zrozumienia i (w moim odczuciu) bardziej intuicyjny.
W wyrażeniu 3 < x < 6
mamy dwa operatory o tym samym priorytecie, a kierunek wiązania dla operatora <
to od-lewej-do-prawej (czyli najpierw obliczana jest wartość tego, co stoi po lewej stronie operatora, a następnie tego co po prawej) – możemy zatem zapisać 3 < x < 6
jako (3 < x) < 6
.
W języku C wartość wyrażenia logicznego to albo 1
albo 0
(typu całkowitego), zatem dla $x = 2$ mamy:
(3 < 2) < 6
⇔ 0 < 6
⇔ 1
Widać zatem, że w przypadku programowania intuicja matematyczna czasem zawodzi – o czym zresztą ostrzega nas kompilator:
D:\cb_projects\sample\main.c|10|warning: comparisons like 'X<=Y<=Z' do not have their mathematical meaning [-Wparentheses]|
#include <stdio.h> #include <stdlib.h> int main() { int n = 5; int k = 3; int iloczyn = 0; int i = 0; // ilosc dotychczas zsumowanych `n` while (i < k) { iloczyn = iloczyn + n; i = i + 1; } printf("%d * %d = %d\n", n, k, iloczyn); return 0; }
Inny sposób – tu zmienna k
określa to, ile razy musimy jeszcze dodać n
!
#include <stdio.h> #include <stdlib.h> int main() { int n = 5; int k = 3; int iloczyn = 0; int k0 = k; // zapamietaj wartosc `k`, bo bedziemy ja modyfikowac... // tu `k` okresla ile `n` pozostalo nam jeszcze do zsumowania! while (k > 0) { iloczyn = iloczyn + n; k = k - 1; } printf("%d * %d = %d\n", n, k0, iloczyn); return 0; }
#include <stdio.h> #include <stdlib.h> int main() { int n; do { printf("Podaj liczbe dodatnia: "); scanf("%d", &n); } while (n <= 0); printf("OK, wpisano %d.\n", n); return 0; }
Na początek kilka słów wstępu:
Pętla do-while
(podobnie jak każda inna pętla – for
, while
) wykonuje się dopóki warunek jest spełniony. Nam zależy na wylosowaniu liczby należącej do przedziału $\langle 10, 15 \rangle$, zatem chcemy wykonywać pętlę dopóki wylosowana liczba nie będzie należeć do przedziału $\langle 10, 15 \rangle$.
Pseudokod (czyli ogólny schemat) naszego programu wygląda zatem następująco:
losuj liczbę `n` DOPÓKI `n` spoza przedziału <10,15> wypisz `n`
Najpierw zastanówmy się, jak wygląda zapis $n \in \langle 10, 15 \rangle$ w języku C. Jak już wiesz, nie możemy napisać 10 <= n <= 15
– musimy rozbić to na dwie części, 10 <= n && n <= 15
.
Jak zatem zapisać warunek spoza przedziału $\langle 10, 15 \rangle$? Można na dwa sposoby:
!(10 <= n && n <= 15)
10 > n || n > 15
.Przykład programu z wykorzystującego sposób #1:
#include <stdio.h> #include <stdlib.h> int main() { int n; do { n = rand() % 101; printf("Wylosowano %d.\n", n); } while (!(10 <= n && n <= 15)); printf("OK, wylosowano %d.\n", n); return 0; }
PS. Nie przejmuj się, że powyższy program zawsze losuje te same liczby w tej samej kolejności – to działanie celowe :)
#include <stdio.h> #include <stdlib.h> int main() { int n = 3; // ilosc stopni int i_levels, i_dollars; for (i_levels = 1; i_levels <= n; i_levels = i_levels + 1) { for (i_dollars = 1; i_dollars <= i_levels; i_dollars = i_dollars + 1) { printf("$"); } printf("\n"); } return 0; }
W powyższym zadaniu zmienne oznaczają:
i_levels
– który z kolei stopień wyświetlamy w danym obiegu pętli (licząc od góry)i_dollars
– który z kolei dolar danego aktualnie “budowanego” stopnia wyświetlamy w danym obiegu pętli (licząc od lewej)
Do tego zadania możemy podejść w następujący sposób – traktujemy pudełko jak kartkę w kratkę o wymiarze $n \times n$ i uzupełniamy kratki wierszami, zaczynając od lewego górnego rogu, wpisując w kratkę *
bądź
.
Zatem wyświetlamy *
, gdy zachodzi dowolny z poniższych warunków:
W języku C taki sposób rozwiązania wygląda następująco:
#include <stdio.h> #include <stdlib.h> int main() { int n = 5; // rozmiar pudelka (dlugosc krawedzi) int i_row; // numer aktualnie uzupelnianego rzedu int i_cell; // numer aktualnie uzupelnianej komorki for (i_row = 1; i_row <= n; i_row = i_row + 1) { for (i_cell = 1; i_cell <= n; i_cell = i_cell + 1) { if (i_row == 1 || i_row == n || i_cell == 1 || i_cell == n || i_cell == i_row) { printf("*"); } else { printf(" "); } } printf("\n"); } return 0; }
Pomysł polega na tym, aby sumować cyfry zaczynając od jedności (z użyciem operatora modulo) i następnie “obcinać” po jednej pozycji – właśnie zaczynając od jedności (z użyciem operatora dzielenia):
196 / 10
wynosi 19
19 / 10
wynosi 9
1 / 10
wynosi 0
Gdy po (kolejnym) dzieleniu dostaniemy wartość 0
– wiemy, że nie zostało już więcej cyfr do sumowania.
#include <stdio.h> #include <stdlib.h> int main() { int n = 196; int n0 = n; // POMYSŁ: "Odcinaj" po jednej cyfrze od końca i zobacz, co // wlasciwie odcinasz ;) int sum = 0; while (n > 0) { sum = sum + n % 10; // dodaj wartość ostatniej cyfry "głowy" n = n / 10; // obetnij ostatnią cyfrę "głowy" } printf("Suma cyfr liczby %d to %d.\n", n0, sum); return 0; }
Krótkie przypomnienie z gimnazjum: Jak rozpoznać czy liczba naturalna jest pierwsza?
(W poniższym przykładzie nie bawimy się w warunek $i < \sqrt{n}$, czyli równoważnie $i^2 < n$, tylko po prostu sprawdzamy wszystkie liczby naturalne z przedziału $[2,n-1]$ – to nie wypływa na poprawność rozwiązania, po prostu poniższy warunek jest mniej wydajny, gdyż należy sprawdzić większą liczbę liczb.)
#include <stdio.h> #include <stdlib.h> int main() { int n; int i; printf("Podaj liczbe calkowita: "); scanf("%d", &n); for (i = 2; (i < n) && (n % i != 0); i = i + 1) ; /* po prostu sprawdz kolejna liczbe... */ if (i == n) { // Cala petla zostala wykonana, wiec liczba N nie byla podzielna // przez zadna z liczb 2, 3, ..., N-1. printf("Liczba %d jest pierwsza.\n", n); } else { printf("Liczba %d NIE jest pierwsza.\n", n); } return 0; }
#include <stdio.h> #include <stdlib.h> int main () { double S = 5.0; double x = S; // krok (1) double x_prev; // wartość `x` w poprzedniej iteracji double accuracy = -10e-3; // pozadana dokladnosc obliczen do { x_prev = x; x = (x_prev + S / x_prev) / 2; // krok (2) printf("%.6f\n", x); } while (-accuracy < (x - x_prev) && (x - x_prev) < accuracy); // krok (3) printf("sqrt(%.2f) ~= %.6f\n", S, x); return 0; }