k cpl 04



Kurs C++ #4



Kurs C++ #4


|========== #04 ==========|
+-------------------------+
| K U R S C + + |
+-------------------------+
s t e r o w a n i e
p r z e b i e g i e m
p r o g r a m u

...czyli autor pokazuje, że nie tylko jedna droga prowadzi do celu.


Instrukcje warunkowe

Pisanie całkowicie liniowych programów jest bezsensowne. Komputer powinien mieć możliwość podejmowania decyzji - wtedy mielibyśmy jakiś efekt porównywania liczb, bo do tej pory nic nam to nie dawało. I tutaj typ bool okaże się bardzo pomocny. Do podejmowania decyzji komputer wykorzystuje instrukcje warunkowe.

Instrukcje if oraz if-else

If to najprostszy typ instrukcji warunkowej. Prototyp instrukcji if wygląda następująco:

if (warunek) instrukcja;

Jeżeli warunek jest prawdziwy (true), zostanie wykonana instrukcja. W przeciwnym wypadku instrukcja zostanie pominięta. Jak już pisałem w lekcji o typach prostych, C++ każdą wartość niezerową traktuje jako prawdę, a zero jest fałszem. Więc możemy zrobić coś takiego (często wykorzystywane przy wskaźnikach):

int *p = new int[10];
if (p) ...

Sztuczka polega na tym, że operator new, gdy nie uda mu się przydzielić pamięci, zwraca wskaźnik o wartości 0. Wobec tego instrukcja po if zostanie wykonana tylko wtedy, gdy wskaźnikowi jest przydzielona pamięć.

Jest jeszcze jedna, bardziej rozbudowana wersja tej instrukcji - instrukcja if-else. Jej prototyp wygląda tak:

if (warunek) instrukcja_1;
else instrukcja_2;

Jeżeli warunek jest spełniony (true), wykonana zostanie instrukcja_1, a instrukcja_2 zostanie pominięta. Gdy zaś warunek nie zostanie spełniony (false), wykonana zostanie tylko instrukcja_2, z pominięciem instrukcja_1. Można również zagnieżdżać instrukcje if-else:

if (warunek_1) instrukcja_1;
else if (warunek_2) instrukcja_2;
else if (warunek_3) instrukcja_3;
else if ...
else instrukcja_n;

Wszystko to bardzo proste, ale (niestety...) znów trzeba uważać: po warunku wykonywana jest tylko jedna instrukcja. A instrukcja kończy się średnikiem, więc nawet, gdy zapiszemy kilka krótkich instrukcji w jednej linijce, to wykonana zostanie tylko pierwsza z nich. Jeżeli korzystamy z if-else, to kompilator od razu zgłosi błąd, jednak przy "okrojonej" instrukcji if - nie będzie żadnego błędu kompilacji. Często nie będzie nawet żadnego widocznego błędu. Tylko potem wyniki są jakieś takie... dziwne...

if (a < 10)
a += 5; // to się wykona
b = a*4; // ale to już nie!
[Ostatnia linijka w zasadzie również się wykona, ale nie jest to zależne od wyniku instrukcji if. Jest to zupełnie osobna część programu niemająca nic wspólnego z funkcją if. - WRIM]

A co zrobić, gdy chcemy po if wykonać kilka instrukcji? Musimy te instrukcje ująć w blok. Blok zawiera się w nawiasach klamrowych i jest traktowany jako jedna instrukcja. Bloki również mogą się zagnieżdżać. W dowolnym bloku możemy zadeklarować zmienną, która jest używana tylko dla tego bloku - są to tzw. zmienne lokalne. Po wykonaniu bloku są one usuwane z pamięci. Wobec tego można korzystać z takiego zapisu:

if (warunek1)
{
  instrukcja1;
  instrukcja2;
  ...
  instrukcjan;
} else
{
  inne_strukcje;
  ...
}

Po nawiasie kończącym blok nie stawiamy średnika. Oczywiście w bloku instrukcji if może znajdować się inna instrukcja warunkowa, która może w sobie zawierać... itd, itp. Nie należy jednak nadużywać bloków instrukcji. Używajmy je tylko wtedy, gdy są naprawdę potrzebne, bo bezsensowne ich wstawianie tylko zaciemnia kod źródłowy.

Instrukcja switch

Instrukcja switch jest alternatywą dla zagnieżdżonych instrukcji warunkowych (if-else if). Czasami dość uciążliwe jest wpisywanie kolejnych "ifów". Wtedy możemy wykorzystać instrukcję switch. Oto jej prototyp:

switch (warunek_liczba_calkowita_lub_enumerator)
{
  case wartosc1:
    instrukcja/e;
    break;

  case wartosc2:
    instrukcja/e;
    break;

  ...
  case wartoscn:
    instrukcja/e;
    break;

  default:
    instrukcja/e;
}

Za słowem switch w nawiasach podajemy argument - liczbę całkowitą lub enumerator (rzadziej warunek, który ma tylko dwie możliwości i wygodniej jest użyć if-else). W bloku instrukcji wymieniamy różne wartości argumentu (po słowie case), jakie obsłużyć ma funkcja. Następnie po dwukropku wpisujemy dowolne instrukcje (również warunkowe) i na końcu słowo break. Słowo to jest potrzebne, bo bez niego instrukcja nie zostałaby przerwana i przeszłaby do obsługi kolejnej wartości, dopóki nie doszłaby do słowa break lub do końca instrukcji switch. Instrukcja default służy do osługi przypadków, które nie zostały wcześniej wyszczególnione - czyli wszystkie pozostałe wartości. Jeżeli w pozostałych przypadkach instrukcja nic nie ma robić, możemy opuścić słowo default. Koniec bloku jest końcem instrukcji. Instrukcja switch jest często wykorzystywana do obsługi menu programowego - i głównie takie właśnie będzie miała dla nas zastosowanie.


Instrukcja ?:

Dość dziwna nazwa instrukcji, prawda? W rzeczywistości jest to skrócona forma instrukcji if-else. Ma ona zastosowanie w dość szczególnych przypadkach. Tak wygląda jej składnia:

warunek ? instrukcja_1 : instrukcja_2;

Jeżeli warunek jest prawdziwy, wykonywana jest instrukcja_1, w przeciwnym wypadku - instrukcja_2. Warto zauważyć, ze instrukcja ?: zwraca wartość, może być więc wykorzystywana w szczególnych przypadkach. Oto przykład jej wykorzystania:

int x, y;
cout << "Podaj dwie wartosci: ";
cin >> x >> y;
cout << endl << "Wieksza z podanych liczb to " << x > y ? x : y << endl;

W tym wypadku wypisywana jest wartość większej liczby. Instrukcja ?: ma ograniczony zakres użycia, jednak często pozwala na zaoszczędzenie nawet kilkunastu linii kodu i nie trzeba stosować niepotrzebnych zmiennych tymczasowych. Pamiętajcie więc o tej instrukcji.


Pętle

Czasem trzeba wykonać tę samą czynność kilkanaście, klikadziesiąt lub nawet kilkaset i więcej razy. Co więcej, często nie wiemy, ile dokładnie razy trzeba to wykonać, bo zależne jest to od wczytanych danych. W tym przypadku pomocne będą pętle. Mamy ich kilka rodzajów, które kolejno omówimy.


Pętla while


Jest to pierwszy i najprostszy typ pętli. Jej prototyp wygląda tak:

while (warunek)
  instrukcja;

Pętla jest wykonywana, dopóki warunek jest spełniony. Warunek jest sprawdzany przed każdym przebiegiem, więc jeśli warunek od początku nie jest spełniony (ma wartość false), pętla nie wykona się ani razu.


Pętla do-while

Kolejny rodzaj pętli o budowie zbliżonej do pętli while. Jednak w tym przypadku warunek jest sprawdzany po każdym przebiegu pętli, a więc mamy gwarancję, że pętla wykona się przynajmniej raz, nawet jeżeli warunek od początku nie był spełniony. Oto prototyp pętli do-while:

do
  instrukcja;
while (warunek);

Oczywiście w każdej pętli zamiast pojedynczej instrukcji możemy użyć bloku instrukcji. koment_new('Czyli instrukcje pisać w nawiasach klamrowych','WRIM',180)


Pętla for

Jest to najbardziej złożona pętla w języku C++. W zasadzie za jej pomocą można stworzyć każdą z już omówionych funkcji, tracąc jedynie na czytelności programu. Tak wygląda jej prototyp:

for (inicjalizacja; warunek; aktualizacja)
  instrukcja;

Teraz czas na kilka słów wyjaśnienia jej działania. Inicjalizacja - to instrukcja wykonywana przed wejściem do pętli. Warunek - dopóki jest spełniony, pętla się wykonuje. Aktualizacja - instrukcja wykonywana po każdym przebiegu pętli. Co ważne, w sekcji inicjalizacji można zadeklarować zmienną, która będzie lokalna tylko dla tej pętli. Często jest to tzw. zmienna sterująca, dzięki której pętla wykonuje się zadaną ilość razy:

for (int i = 0; i < 10; i++)
  cout 1000; ) ...

// pętla bez warunku
for (int j = 9 ;; j--) ...

// pętla nieskończona
for (;;) ...
for (; true ;) ... // warunek zawsze spełniony


Instrukcje break i continue

Poznaliśmy już możliwość tworzenia pętli nieskończonych - jednak nie jesteśmy w stanie ich przerwać. Aby tego dokonać, stworzono słowo kluczowe break. Poznaliśmy je już przy instrukcji switch, jednak tutaj pełni ono nieco inną rolę: powoduje przerwanie wykonywania pętli i przejście do instrukcji następnej po pętli. Jest jeszcze jedno słowo kluczowe, charakterystyczne tylko dla pętli: instrukcja continue. Powoduje ona na ominięcie pozostałych instrukcji w pętli i rozpoczęcie nowego przebiegu pętli, a wcześniej sprawdzenie warunku.

Teraz spróbujemy napisać program, w którym wykorzystamy naszą dotychczasową wiedzę o programowaniu w C++. Stworzymy prosty kalkulator:

kalkulator.cpp

#include <iostream>
#include <cstdlib>

using namespace std;

enum DZIALANIE {
    dod, odejm, mnoz, dziel
};

int main(int argc, char *argv[])
{
    cout << "Kalkulator ver. 0.1 by .:ArchiE:. (c) 2006" << endl << endl;
    
DZIALANIE dzial;
    while (true) // nieskończona pętla wczytująca rodzaj działania
    {
        cout << "Wybierz rodzaj dzialania:";
        cout << "\n+ > Dodawanie\n- > Odejmowanie\n* > Mnozenie\n/ > Dzielenie";
        cout << "\n\nTwoj wybor: ";
        char c;
        cin >> c;
        switch (c)
        {
            case '+':
                dzial = dod;
                break;
            case '-':
                dzial = odejm;
                break;
            case '*':
                dzial = mnoz;
                break;
            case '/':
                dzial = dziel;
                break;
            default: // jeśli podano inny znak - powtórz pętlę
                cout << "\a\n\nWybrano zly rodzaj dzialania. Ponow probe.\n" << endl;
                continue;
        }
        cout << endl << endl;
        break;
    }
    
    double x, y;
    cout << "Podaj pierwsza liczbe:\t";
    cin >> x;
    cout << "\nPodaj druga liczbe:\t";
    cin >> y;
    cout << endl << endl;
    
    if (dzial == dod)
       cout << "Wynik dzialania: " << x << " + " << y << " = " << x+y << endl << endl;
    else if (dzial == odejm)
       cout << "Wynik dzialania: " << x << " - " << y << " = " << x-y << endl << endl;
    else if (dzial == mnoz)
       cout << "Wynik dzialania: " << x << " * " << y << " = " << x*y << endl << endl;
    else if (dzial == dziel)
       if (y >= -0.00000001 && y <= 0.00000001)
        cout << "\aNie mozna dzielic przez zero!" << endl << endl;
       else
        cout << "Wynik dzialania " << x << " / " << y << " = " << x/y << endl << endl;
    
    system("PAUSE");
    return EXIT_SUCCESS;
}

Dla osób znających C++ niektóre rozwiązania w tym programie mogą wydać się udziwnione - i będą mieli rację. Jest to jednak zrobione celowo, aby pokazać "w akcji" większość elementów, które już poznaliśmy. Jak widać, gdy była potrzeba wielokrotnego wyboru, użyłem raz instrukcji switch, a raz zagnieżdżonych konstrukcji if-else. W ten sposób możemy zauważyć różnicę między tymi dwoma elementami.

Teraz zinterpretujemy działanie naszego programu. Na samym początku deklarujemy typ enumeryczny DZIALANIE - robimy to przed funkcją main(). Moglibyśmy zrobić to wewnątrz tej funkcji - nie miałoby to większego znaczenia. Jednak gdy przejdziemy do omawiania funkcji, zauważycie, że ten typ istniałby tylko dla funkcji main() - w innych funkcjach niemógłby być wykorzystywany.

Dalej mamy deklarację funkcji main() oraz zmiennej dzial typu DZIALANIE - a więc tworzymy enumerator. Następna instrukcja może być dość dziwna - nieskończona pętla (while (true)). Służy ona do wczytywania typu działania dla naszego kalkulatora. Prześledźmy jej działanie.

Na początku pętla wyprowadza na ekran prośbę o podanie działania. Następnie wczytuje jeden znak, który jest sprawdzany przez instrukcję switch. Jeżeli znak działania jest prawidłowy, ustawiana jest odpowiednia wartość zmiennej dzial i opuszczana jest instrukcja switch. Jeżeli działanie jest nieprawidłowe (instrukcja default), zostaje wykonana instrukcja continue, która, jak już wiemy, powoduje skok do następnego przebiegu funkcji. Po podaniu prawidłowego działania instrukcja switch jest opuszczana i następuje wykonanie kolejnej instrukcji pętli, którą w tym przypadku jest instrukcja break - czyli opuszczenie pętli.

Następnie deklarujemy dwie zmienne typu double - x i y, których wartości następnie wczytujemy. Następnie mamy sekwencję instrukcji if-else, aby wykonać odpowiednie działanie. Zauważmy, że nie używamy tutaj zmiennej odpowiadającej za wynik działania, tylko wyświetlamy go bezpośrednio na ekranie. Należy również pamiętać o tym, że nie można dzielić przez zero. Wtedy liczba zmiennoprzecinkowa ma wartość wartość INFINITE (nieskończoność), a nie wszystkie kompilatory potrafią tę wartość odpowiednio interpretować. W C++ Builderze generowany jest wyjątek, co na razie dla nas oznacza zakończenie programu z błędem. Natomiast w Dev-C++ standardowo nie jest włączona obsługa wyjątków, więc interpretuje on po prostu wartość INFINITE. Gdy włączymy obsługę wyjątków, efekt będzie podobny, jak w kompilatorze Borlanda. Należy jednak jak ognia unikać dzielenia przez 0. Nawet w matematyce jest to działanie, którego należy unikać. Dlatego w tym programie stosujemy warunek sprawdzający, czy dzielnik nie jest zerem. Możemy zauważyć, że nie zastosowałem tu bezpośredniego porównania, jak w liczbach całkowitych, tylko sprawdzenie, czy liczba mieści się w podanym zakresie - dlaczego nie należy tego robić, wyjaśniałem w lekcji drugiej.

W tym miejscu zainteresowanym daję zadanie - spróbujcie rozbudować nasz kalkulator, dodając obsługę innych działań - proponuję potęgowanie (znak ^) oraz silnię (!) koment_new("Kiedyś napisałem takie coś w JavaScripcie","WRIM",200). Dla tych, którzy nie wiedzą lub nie pamiętają: silnia przyjmuje tylko jedną liczbę i liczymy tylko na liczbach całkowitych. Wypadałoby sprawdzić, czy podano liczbę całkowitą, czy nie, ale równie dobrze liczba może być zaokrąglana. Silnię liczymy w ten sposób:

2! = 2 * 1
5! = 5 * 4 * 3 * 2 * 1
8! = 8 * 7 * 6 * 5 * 4 * 3 * 2 * 1

Mogę podpowiedzieć, że bez pętli for się nie obejdziecie. Autorzy poprawnych rozwiązań zostaną publicznie wymienieni w AMK - tak, aby cały świat się dowiedział o rodzących się nowych programistycznych talentach. :) Bardzo ambitni mogą również pokusić się o dodanie jeszcze obsługi dzielenia całkowitego oraz reszty z dzielenia (modulo). Przypominam, że operacje te mogą odbywać się tylko na liczbach całkowitych.


Ta część kursu jest tak jakoś śmiesznie mała w porównaniu do poprzednich, ale na prawdę nie mam już nic istotnego do dodania - wobec tego kończę na dziś i czekam na wasze maile z odpowiedziami, pytaniami, pomysłami itp. Na każdy rzetelny list postaram się odpowiedzieć. Jeszcze raz przypominam, że na czyjąś prośbę mogę przesłać poprzednie części kursu. I zapraszam do lektury kolejnej lekcji, w której powiemy sobie o funkcjach.

autor("ArchiE","archie007@wp.pl")

PS. W związku z małą objętością tej częsci kursu, postanowiłem dołączyć dodatek - systemy liczbowe i operacje bitowe. Warto przeczytać, warto poznać, warto wiedzieć. :)



Wyszukiwarka

Podobne podstrony:
k cpl
k cpl2
k cpl?
k cpl1
k cpl?
k cpl?
k cpl
k cpl
r08 cpl t (3)
t p cpl
k cpl0
k cpl0
k cpl
k cpl
k cpl1
k cpla

więcej podobnych podstron