User Tools

Site Tools


dydaktyka:cprog:2015:loops

This is an old revision of the document!


Pętla "while", formatowanie kodu, debuggowanie

Cel laboratorium

  • Omówienie priorytetów operatorów.
  • Zapoznanie z pętlą while i do-while.
  • Zasady formatowania kodu.
  • Umiejętność posługiwania się debuggerem.

Priorytet operatorów

Priorytet operatorów oznacza to samo, co kolejność działań w matematyce – operator o wyższym priorytecie zostanie wykonany najpierw, a gdy obok siebie stoją, to operacje są wykonywane od lewej do prawej (chyba, że sam operator działa inaczej, o czym za chwilę).

Przykładowo – w matematyce – wyrażenie $(2+2 \cdot 2) \cdot 2$ zostanie obliczone w następujących krokach:

  1. Obliczenie wartości w nawiasie:
    1. Obliczenie $2 \cdot 2 = 4$
    2. Obliczenie $2 + 4 = 6$
  2. Obliczenie $6 \cdot 2 = 12$

Dlaczego tak? Ponieważ – mówiąc językiem programisty – nawiasy mają najwyższy priorytet, a mnożenie ma wyższy priorytet od dodawania.

Zapoznaj się z tabelą priorytetów operatorów w języku C (uwaga: wielu z zawartych w niej operatorów jeszcze nie znasz, lecz część z nich wprowadzimy sukcesywnie na kolejnych zajęciach).
Zwróć uwagę na kolumnę Associativity, czyli “kierunek wiązania”. Oznacza to, która strona operatora zostanie obliczona w pierwszej kolejności (np. Left-to-right oznacza, że najpierw obliczona zostanie lewa strona operatora) i niejako “w którą stronę działa operator” – przykładowo operator przypisania = przypisuje wartość stojącą po jego prawej stronie zmiennej stojącej po lewej stronie, zatem jego kierunek wiązania to Right-to-Left.

Tak więc, można stwierdzić, że każde wyrażenie w języku C ma pewną wartość, obliczoną zgodnie z priorytetami i kierunkami wiązania operatorów.

:!: Nie musisz pamiętać całej tej terminologii, lecz musisz kojarzyć o co w tym wszystkim chodzi.


Zadanie 1
Napisz program sprawdzający, czy dla zadanej przez użytkownika liczby $x$ zachodzi $4 < x < 6$.
Przetestuj swój program dla $x = 5$.

Pętla "while"

Pętla while wykonuje instrukcję dopóty, dopóki spełniony jest warunek testowy (tzn. wyrażenie podane jako warunek ma wartość niezerową).

Ogólna postać:

while (wyrażenie_testowe)
    instrukcja

Pętla while to pętla z warunkiem wejścia, co oznacza że warunek sprawdzany jest przed każdym jej obiegiem (także pierwszym).

Zazwyczaj zechcesz gdzieś wewnątrz pętli zmieniać wartości zmiennych tak, aby warunek wejścia stał się fałszywy :)


Przykład
Obliczanie silni za pomocą pętli while – porównaj z programem wykonującym to samo zadanie z użyciem pętli for.

#include <stdio.h>
 
int main()
{
    int s, n, i;
 
    printf("Podaj liczbe naturalna n: ");
    scanf("%d", &n);
 
    s = 1;
    i = 1;
 
    while (i <= n) {
        s *= i;
        i = i + 1;
    }
 
    printf("%d! = %d\n", n, s);
 
    return 0;
}

Która pętla Twoim zdaniem lepiej nadaje się do tego zadania?

Dobra praktyka programistyczna mówi:
Stosuj pętlę for tylko wtedy, gdy dokonujesz zarówno inicjalizacji, jak i aktualizacji. We wszystkich innych przypadkach lepiej (czytelniej) użyć pętli while.


Zadanie 1
Napisz program obliczający wynik $n \cdot k$ dla liczb całkowitych $n$ i $k$. Użyj pętli while.
Wskazówka: $n \cdot k = \underbrace{n + n + \ldots + n}_{k \text{ razy}}$

Pętla "do-while"

Pętla do-while, podobnie jak pętla while wykonuje instrukcję dopóki spełniony jest warunek testowy.

Ogólna postać:

do
    instrukcja
while (wyrażenie_testowe);

:!: Zwróć uwagę na średnik występujący po while (...)!

Różnica między dwoma typami pętli while polega na tym, że do-while to pętla z warunkiem wyjścia – oznacza to, że warunek sprawdzany jest po każdym obiegu pętli (a więc petla wykona się co najmniej raz).


Przykład

#include <stdio.h>
 
int main(void)
{
    int i = 0;
 
    printf("Poczatek petli \n\n");
 
    do {
        printf("Wartosc zmiennej 'i' wynosi %d \n", i);
        i = i + 1;
    } while (i < 5);
 
    printf("\nKoniec petli");
 
    return 0;
}

W praktyce programiści starają się unikać stosowania do-while, gdyż:

  • umieszczanie warunku na końcu pętli zmniejsza czytelność kodu, natomiast
  • bez trudu można tak zmienić kod, aby zastąpić pętlę do-while pętlą while.

Uogólnienie powyższej zasady brzmi:
Lepiej stosować pętle z warunkiem wejścia (while, for), a nie wyjścia (do-while).   (z tych samych powodów, co podane powyżej)


Zadanie 1
Napisz program, który prosi użytkownika o podanie liczby całkowitej większej od $0$. Program powinien zakończyć działanie dopiero wtedy, gdy użytkownik podał poprawną wartość.

Zadanie 2
Napisz program losujący liczbę z przedziału $[0;100]$ tak długo aż wylosuje liczbę z przedziału $[10;15]$.
Do losowania liczb służy funkcja rand().

Formatowanie kodu

Jedną z zasad tworzenia czytelnego kodu jest umiejętne stosowanie wcięć (indent), które oznaczają “poziom zagłębienia” kodu.
Choć kompilator C pomija znaki białe (a więc m.in. wcięcia i znaki nowej linii), to jednak dla człowieka taki kod byłby ciężki do odczytania.

Zasada brzmi:

  • początek nowego bloku instrukcji oznacza zwiększenie ilości wcięć o jeden poziom
  • koniec bloku instrukcji oznacza zmniejszenie ilości wcięć o jeden poziom

Zazwyczaj stosuje się wcięcie szerokości jednego tabulatora bądź czterech spacji.

Przykład:

int main () {
--->if (1) {
--->--->printf("Hello");
--->--->printf("World!\n");
--->}
}

:!: Kwestia formatowania to rzecz umowna – istnieją różne szkoły formatowania, a programiści toczą o to nieustanne “święte wojny”. Najważniejsza zasada brzmi: Stosuj reguły konsekwentnie!

Autoformatowanie kodu z użyciem IDE

Całą tą “czarną robotę” związaną z formatowaniem może za Ciebie IDE (tu: CodeBlocks).

Aby automatycznie sformatować kod, kliknij prawym przyciskiem myszy na oknie edytora tekstu i wybierz z menu kontekstowego opcję Format use AStyle.

Aby zmienić ustawienia autoformatowania wejdź w Settings → Editor… i z listy z ikonami po lewej wybierz Source formatter.
W przykładach na zajęciach stosuję następujące opcje formatowania:

  • zakładka Style
    • Bracket style: K&R
  • zakładka Indentation
    • (bez zmian)
  • zakładka Formatting
    • Pad empty lines around header blocks (e.g. 'if', 'while'…) – zaznaczone
    • Insert space padding around operators – zaznaczone
    • (pozostałe opcje odznaczone)

Debuggowanie

Zdarza się, że choć program nie zawiera błędów składniowych (czyli kompiluje się bez problemów i da się go uruchomić), to jednak nie działa zgodnie z oczekiwaniami. W takich sytuacjach do zdiagnozowania przyczyny problemu przydaje się narzędzie o nazwie debugger.

Debugger (nazwa czasem tłumaczona na polski jako “odpluskwiacz” :-)) służy do krokowego wykonania programu (czyli instrukcja po instrukcji), z możliwością podglądu stanu programu (a więc m.in. wartości zmiennych) po każdym kroku.

:!: Debugger nie jest częścią języka – to narzędzie dostarczane (zazwyczaj) w ramach zintegrowanego środowiska programistycznego.

Podczas procesu debuggowania korzysta się z breakpoint-ów, które mówią debuggerowi tyle – “koniecznie zatrzymaj się tutaj”.

(Ciekawi Cię, skąd się wzięła nazwa bug? – Przeczytaj w wolnej chwili ten krótki fragment.)

Debuggowanie w CodeBlocks

Do nauki wykorzystaj poniższy kod:

#include <stdio.h>
#include <stdlib.h>
 
int main()
{
    int i;
    int n = 2;
 
    printf("Zaczynamy...\n");
 
    for (i = 1; i < 5; i++) {
        n = n * n;
    }
 
    printf("Wynik = %d\n", n);
}

(W razie problemów z uruchomieniem debuggera zaglądnij do FAQ.)

Debuggowanie krok po kroku:

  1. Aby móc przystąpić do debuggowania, dodaj choć jeden breakpoint. W tym celu przejdź do linijki z pierwszą instrukcją printf() i wybierz Debug → Toggle breakpoint – po lewej stronie, koło numeru wiersza, pojawi się czerwona kropka (symbol breakpoint-u).
  2. Wybierz Debug → Start/Continue – uruchomiony zostanie proces debuggowania.
  3. Aby zobaczyć podgląd zmiennych, wybierz Debug → Debugging windows → Watches
  4. Przejdź program krok po kroku (Debug → Next line), aż do jego zakończenia, i obserwuj zmiany wartości zmiennych oraz zawartości okna konsoli.

W oknie Watches w pierwszej kolumnie możesz podać w wolnym polu u dołu dowolne poprawne wyrażenie – jego wartość zostanie automatycznie obliczona zgodnie z bieżącym stanem programu.
Dodaj wyrażenie x + 8 < 20 i ponownie prześledź zmianę wartości tego wyrażenia podczas kolejnych obiegów pętli.

Czasem interesuje Cię podgląd stanu programu tylko w kilku określonych miejscach – dodaj w nich breakpointy, a następnie przechodź pomiędzy nimi (czyli wykonuj automatycznie wszystkie instrukcje pomiędzy obecnym, a kolejnym breakpointem) za pomocą Debug → Start/Continue.
Dodaj drugi breakpoint przy drugim wywołaniu printf(). Uruchom debuggowanie, a następnie ponownie wybierz Debug → Start/Continue.

:!: Pod kątem dalszych studiów na kierunku Inżynieria Biomedyczna zapamiętaj – MATLAB także posiada debugger! :-D

Zadania podsumowujące

Zadanie 1

(poziom trudności: )

Napisz program, który wypisze na ekran taki fragment:

1 2 3
2 3 1
3 1 2

Użyj pętli for.

Wskazówka: Pętle, podobnie jak instrukcje warunkowe, można zagnieżdżać. Pętla wewnętrzna wykonuje swój pełny zakres powtórzeń w każdym cyklu pętli zewnętrznej.

Zadanie 2

(poziom trudności: )

Napisz program, który dla zadanej liczby całkowitej obliczy sumę jej cyfr.
Przykład: dla $196$ poprawnym wynikiem jest $1 + 9 + 6 = 16$

Wskazówka: Wykorzystaj własność dzielenia i obcięcia w języku C.

Zadanie 3

(poziom trudności: )

Napisz program obliczający pierwiastek kwadratowy zadanej liczby metodą babilońską. Przerwij obliczenia, gdy różnica między kolejnymi przybliżeniami jest mniejsza niż $10^{-4}$.

Wskazówka: Jako pierwsze oszacowanie możesz przyjąć $\sqrt{S} \approx S$ ;-)

Być może zastanawiasz się: Po co ręcznie obliczać pierwiastek? Przecież można skorzystać z wbudowanej funkcji sqrt()!
Owszem, lecz jeśli nie potrzebujesz mega-dużej dokładności, to metodą babilońską uzyskasz wystarczająco dobry wynik o wiele, wiele szybciej.

dydaktyka/cprog/2015/loops.1446071133.txt.gz · Last modified: 2020/03/25 11:46 (external edit)