Modul3, Courseware Development Tools


Instrukcja wyboru if...else.
Obsługa wyjątków

0x08 graphic
Przegląd zagadnień

W programie w zależności od warunków - wartości pewnych zmiennych, należy podjąć decyzję, czy pewne instrukcje mają być wykonane albo nie. W programie do podjęcia tej decyzji używamy instrukcji wyboru. W tym rozdziale poznamy pierwszą instrukcję wyboru if ... else. Druga instrukcja wyboru switch zostanie przybliżona w rozdziale piątym.

Podczas konstrukcji algorytmu zakładamy pewne warunki początkowe (np.: dopuszczalne wartości zmiennych, nieograniczona pamięć, dostęp do dysku, dostęp do sieci itp.). Bywa, że z przyczyn zewnętrznych lub błędów programisty lub użytkownika warunki te nie są spełnione. Do obsługi takich sytuacji awaryjnych w języku C# ( a tak naprawdę w całym .Net Framework) służą wyjątki.

Po skończeniu tego modułu studenci powinni:

0x08 graphic
Instrukcja wyboru if ... else

Instrukcja wyboru if ... else jest pierwszą instrukcją sterującą języka C# omówioną w tym kursie.

W tej części modułu studenci:

0x08 graphic
Definicja instrukcji if ... else

Instrukcja warunkowa if może mieć dwie formy:

  1. if(warunek)
    instr1;

  2. if(warunek)
    instr1;
    else
    instr2;

W przypadku pierwszym działanie instrukcji if jest następujące. Jeżeli warunek ma wartość true, wtedy instrukcja instr1 jest wykonywana. Natomiast, gdy warunek ma wartość false, instrukcja instr1 jest pomijana.
W przypadku drugim, gdy warunek ma wartość true, wtedy instrukcja instr1 jest wykonywana, ale instrukcja instr2 jest pomijana. Natomiast, gdy warunek ma wartość false, instrukcja instr1 jest pomijana, ale jest wykonywana instrukcja instr2.

Wcięcia użyte w powyższym kodzie oczywiście są opcjonalne, ale bardzo zwiększają czytelność kodu. Pokazują zależności.

Częstym błędem popełnianym przez początkujących programistów jest postawienie średnika tuż za nawisem "zamykającym" warunek.

if(warunek) ;
instr1;

Nie jest to błąd składni, więc kompilator nie zgłosi błędu, chociaż zgłosi ostrzeżenie. W kodzie powyższym (ze średnikiem "po if") instrukcja instr1 będzie wykonywana zawsze, bez względu na wartość wyrażenia warunek. Od wartości wyrażenia warunek zależy tylko tzw. instrukcja pusta.

Zazwyczaj chodzi o wykonanie warunkowe nie pojedynczej instrukcji, ale całego ciągu instrukcji. Musimy umieścić wtedy wszystkie instrukcje w bloku instrukcji (bloku kodu).

if(warunek)
{
instrT1;
...
instrTn
}
else
{
instrN1;
...
instrNn;
}

Stosowanie nawiasów klamrowych (bloku kodu) jest dozwolone również w przypadku, gdy od wartości wyrażenia warunek zależy wykonanie pojedynczej instrukcji. Zwiększa to czytelność kodu i pozwala uniknąć szeregu błędów.

Wyrażenie warunek musi mieć wartość logiczną, true lub false. Nie może być to. wartość numeryczna, jak w języku C/C++.
W wyrażeniu warunek stosujemy często operatory relacyjne i porównania w połączeniu z operatorami logicznymi, np:

...
if(x > 10 && x < 100)
{
Console.WriteLine("x należy do przedziału
¬(0; 100).");
}

w powyższym kodzie sprawdzamy czy x należy do przedziału (0; 100), czyli czy10<x<100. Wyrażenie 10<x<100 w języku C# powoduje błąd kompilacji.
Poniższy kod natomiast sprawdza czy x należy do sumy przedziałów:

...
if(x < 10 || x > 100)
{
Console.WriteLine("x jest mniejsz od 10 lub
¬większe od 100");
}

Zagnieżdżone instrukcje if ... else

0x08 graphic

Instrukcja if może być umieszczona zarówno w bloku kodu "tuż po if" jak i w bloku kodu po else.

if(warunek1)
{
...
if(warunek2)
{
...
}
...
}
else
{
...
if(warunek3)
{
...
}
...
}

Stosowanie pojedynczej konstrukcji if ... else daje możliwość dwuwariantowego wyboru. Stosując jednak zagnieżdżoną instrukcję if w bloku else można uzyskać możliwość wielowariantowego wyboru.

if(warunek1) instrukacja1;
else if(warunek2) instrukcja2;
else if(warunek3) instrukcja3;
else if(warunek4) instrukcja4;
else instrukcja5;

W powyższym kodzie będzie wykonana albo instrukcja1 albo instrukcja2 albo instrukcja3 albo instrukcja4 albo instrukcja5. instrukcja5 będzie wykonana tylko wtedy, gdy żaden warunek nie będzie prawdziwy (każdy będzie miał wartość false).

Stosując zagnieżdżenie warto zwrócić uwagę, że instrukcja else skojarzona jest z najbliższą instrukcją if. Rozważmy następujący przykład:

if(warunek1)
if(warunek2)
instrukcja1;
else
instrukcja2;

W powyższym kodzie układ graficzny (wcięcia) sugerują, że instrukcja else jest związana z instrukcją if(warunek1), ale naprawdę jest skojarzona z instrukcją if(warunek2), czyli aby układ graficzny odzwierciedlał rzeczywiste zależności w kodzie, należałoby go zapisać:

if(warunek1)
if(warunek2)
instrukcja1;
else
instrukcja2;

Jeżeli jednak instrukcja else powinna być powiązana z pierwszą instrukcją if kod powinie wyglądać następująco:

if(warunek1)
{
if(warunek2)
instrukcja1;
}
else
instrukcja2;

0x08 graphic
Demo: Czy punkt należy do koła?

0x08 graphic
Uruchom Visual Studio. Otwórz projekt CzyWKole. Projekt ten jest częścią rozwiązania Kurs\Demo\Modul3\Modul3.sln, gdzie Kurs jest katalogiem gdzie skopiowano pliki kursu.

0x08 graphic
0x08 graphic

0x08 graphic
0x08 graphic
0x08 graphic
0x08 graphic

0x08 graphic
0x08 graphic
0x08 graphic
0x08 graphic
0x08 graphic

Skompiluj i uruchom program. Jako współrzędne punktu podaj wartości x = 2 i y = 2. Jest to punkt P1. Na ekranie pojawi się napis:

Punkt należy do koła

Uruchom program po raz drugi. Tym razem podaj następujące wartości x = 5, y = 2 - punkt P2. Na ekranie pojawi się napis:

Punkt nie należy do koła

W programie jest użyta metoda Sqrt kasy Math. Oblicza ona pierwiastek kwadratowy np.:

double x = Math.Sqrt(4);

Powyższa linijka spowoduje obliczenie pierwiastka kwadratowego z czterech i następnie podstawienie obliczonej wartości do zmiennej x.. Czyli x będzie miało wartość dwa.

W programie występuje linijka:

0x08 graphic
double odleglosc = Math.Sqrt((x - SrodekX) *
(x - SrodekX)+(y - SrodekY)*(y - SrodekY));

którą można "matematycznie" zapisać

Zmienna odległosc będzie zawierać odległość punktu o współrzędnych podanych przez użytkownika od środka koła.

0x08 graphic
Wyjątki

Zanim przejdziemy do omawiania implementacji wyjątków w języku C#, warto wspomnieć, że dużą zaletą .Net Framework jest standaryzacja obsługi wyjątków w wielu językach. Wyjątek zgłoszony w kodzie napisanym w języku C# może zostać obsłużony w kodzie napisanym w języku Visual Basic i na odwrót.

Mechanizm obsługi wyjątków powstał w celu rozwiązania następującego problemu: W pewnym miejscu programu wykrywamy błąd, pewną niezgodność z oczekiwaniami. Nie wiemy jednak jak wykryty błąd poprawić w miejscu jego znalezienia. Przerywamy więc wykonywanie programu i szukamy miejsca w programie, gdzie jest podane jak z danym błędem sobie poradzić. W przypadku gdy w programie nie ma przepisu jak z danym błędem sobie poradzić lub jest on w nieodpowiednim miejscu, program zostaje zamknięty i na ekranie pojawia się okno dialogowe z komunikatem opisującym przyczyny zakończenia programu.

W celu demonstracji zgłoszenia wyjątku można wykorzystać projekt Dzielenie, który jest częścią rozwiązania Kurs\Demo\Modul3\Modul3.sln, gdzie Kurs jest katalogiem gdzie skopiowano pliki kursu. W celu wymuszenia zgłoszenia wyjątku jako wartość dzielnika należy podać 0 (zero).

W tej części modułu studenci:

Typy wyjątków

0x08 graphic

Wyjątki w języku C# reprezentowane są przez obiekty - typy referencyjne. Na slajdzie pokazaną mamy pewną hierarchię, jaką tworzą klasy wyjątków. Strzałka pokazuje klasę wyjątku bardziej ogólną. Wyjątek bardziej ogólny może reprezentować wszystkie wyjątki, które go uszczegóławiają.

Klasa Exception jest najbardziej ogólną klasą wyjątku i może reprezentować wszystkie wyjątki.
Klasa ApplicationException reprezentuje wszystkie wyjątki specyficzne dla danej aplikacji. Definicja specyficznych wyjątków, czyli własnych, jest poza zakresem tego kursu.
Klasa SystemException jest klasą ogólną dla pewnej części predefiniowanych wyjątków np.:

Więcej klas reprezentujących wyjątki można znaleźć w MSDN LiIbrary pod tematem "Exception Hierarchy".

Uwaga:
Na temat tworzenia hierarchii klas, czyli pojęcie dziedziczenie, opisane jest dokładnie w kursie "Programowanie obiektowe".

0x08 graphic
Użycie bloków try i catch

W przypadku, gdy pewien fragment kodu może powodować zgłoszenie wyjątku, a my chcemy go obsłużyć, czyli wiemy jak z danym błędem sobie poradzić, umieszczamy dany fragment kodu w bloku try. Możemy powiedzieć, że kod jest pilnowany lub próbowany na wystąpienie ewentualnych błędów. Z każdym blokiem try musi być związany, co najmniej jeden blok catch lub finally. O bloku finally będzie mówione w dalszej części tego rozdziału. W po słowie catch w nawiasach okrągłych podajemy typ wyjątku, który chcemy obsłużyć. W bloku kodu instrukcji catch podajemy kod obsługi wyjątku.

try
{
//kod "pilnowany"
}
catch(TypWyjatku ex)
{

//kod obsługi wyjątku
}

Identyfikator ex jest uchwytem do zgłoszonego obiektu wyjątku.

Jeżeli podczas wykonywania kodu pilnowanego nie zostanie zgłoszony żaden wyjątek, to instrukcje bloku catch (kod obsługi wyjątku) zostają pominięte. W przypadku, gdy podczas wykonywania kodu pilnowanego zostanie zgłoszony wyjątek typu takiego samego, jak który jest podany w instrukcji catch lub typu, dla którego typ podany w instrukcji catch jest typem ogólnym (bazowym), wtedy wykonanie kodu pilnowanego zostaje przerwane i przechodzimy do wykonywania kodu obsługi wyjątku. Pamięć zarezerwowana dla wszystkich zmiennych zadeklarowanych w bloku try zostaje zwolniona, czyli zmienne lokalne zadeklarowane w bloku try zostają zniszczone. Po wykonaniu wszystkich instrukcji kodu obsługi wyjątku program przechodzi do wykonania instrukcji znajdujących się za blokiem catch, oczywiście pod warunkiem, że w kodzie obsługi wyjątku nie został zgłoszony żaden wyjątek.
W przypadku, gdy typ zgłoszonego wyjątku jest niezgodny z typem wyjątku podanym w instrukcji catch, następuje próba znalezienia zewnętrznego bloku try, w którym omawiany wcześniej bloki try oraz catch są zawarte. Jeżeli taki zewnętrzny blok zostanie znaleziony, sprawdzane jest, czy typ podany w instrukcji catch związanej z zewnętrznym blokiem try, jest zgodny z typem zgłoszonego wyjątku. Jeżeli typ jest zgodny, program przechodzi do wykonania kodu obsługi wyjątku (oczywiście tego zewnętrznego), w przeciwnym razie szukamy kolejnego zewnętrznego bloku try. Poszukiwania zewnętrznego bloku try trwają aż do napotkania końca metody Main. Jeżeli nie zostanie znaleziony blok try, z którym skojarzona jest instrukcja catch z odpowiednim typem wyjątku, program zostaje przerwany i zostaje wyświetlony odpowiedni komunikat o kłopotach z danym programem.

Jeżeli chcemy przechwycić wszystkie zgłoszone wyjątki w danym bloku try, w instrukcji catch nie podajemy typu wyjątku:

try
{
...
}
catch
{
...
}

Rozwiązanie to uniemożliwia jednak uzyskanie dostępu do obiektu wyjątku, który może zawierać ważne informacje na temat błędu. W celu przychwycenia wszystkich wyjątków oraz uzyskania dostępu do informacji o błędzie, wyjątki należy obsługiwać w następujący sposób:

try
{
...
}
catch(System.Exception ex)
{
...
}

0x08 graphic
Użycie wielu bloków catch

Z pojedynczym blokiem try może być skojarzonych więcej bloków catch. Zgłoszony wyjątek może być obsłużony przez pojedynczy blok cache. Wyjątek zostanie obsłużony przez ten pierwszy blok cache, którego typ jest zgodny z typem zgłoszonego wyjątku. Ze względu na powyższe, niedozwolone jest umieszczanie bloku catch z typem wyjątku ogólnym, przed blokiem catch z typem wyjątku bardziej szczegółowym, ponieważ kod obsługi wyjątku szczegółowego nigdy nie zostałby wykonany.

0x08 graphic
Blok finally

Często się zdarza, że chcemy wykonać pewne instrukcje bez względu na to, czy nie było zgłoszonego wyjątku, czy wyjątek był zgłoszony i został obsłużony lub czy został zgłoszony i nieprzechwycony.

Instrukcje, które muszą być zawsze wykonane na zakończenie bloku try umieszczamy wewnątrz bloku finally. Blok finally może wystąpić tuż za blokiem try, gdy z blokiem try nie ma związanego żadnego bloku catch albo po wszystkich blokach catch skojarzonych z danym blokiem try.

try
{
...
}
catch(Exception ex)
{
...
}
finally
{
...
}

Demonstracja

Uruchom Visual Studio. Otwórz projekt Finally. Projekt ten jest częścią rozwiązania Kurs\Demo\Modul3\Modul3.sln, gdzie Kurs jest katalogiem gdzie skopiowano pliki kursu.

Skompiluj i uruchom program.

Przy pierwszym uruchomieniu jako wartość dzielnika podaj 5.
Zwróć uwagę, że żaden wyjątek nie został zgłoszony. Instrukcja bloku finally została wykonana. Zakończ program.

Uruchom program ponownie. Jako wartość dzielnika podaj qwerty. Podanie takiej wartości spowoduje zgłoszenie wyjątku FormatException. Zwróć uwagę, że został przechwycony wyjątek i instrukcja bloku finally została wykonana. Zakończ program.

Uruchom program ponownie. Jako wartość dzielnika podaj 0. Podanie takiej wartości spowoduje zgłoszenie wyjątku DivideByZeroException. Zwróć uwagę, że wyjątek nie został przechwycony, ale instrukcja bloku finally została wykonana. Zakończ program.

0x08 graphic
Zgłaszanie wyjątków

Wyjątek możemy zgłosić samodzielnie. Służy do tego instrukcja throw:

throw new TypWyjatku();

Z wyjątkiem możemy skojarzyć również pewien komunikat, informujący o źródłach błędu. Dokonujemy to w następujący sposób:

throw new TypWyjatku("Treść komunikatu");

Jak skorzystać z komunikatu, pokazane zostanie w przykładzie poniżej:

double dzielnik, iloraz;
try
{
dzielnik = Convert.ToDouble(Console.ReadLine());
if(dzielnik == 0)
throw new DivideByZeroException("Dzielnik nie
¬może mieć wartość zero!!!");
iloraz = 10.2 / dzielnik;
Console.WriteLine(iloraz);
}
catch (DivideByZeroException ex)
{
Console.WriteLine("Komunikat wyjatku: {0}",
ex.Message);

}

Wewnątrz bloku catch można ponownie zgłosić wyjątek, który jest właśnie obsługiwany. W ten sposób będzie go można obsłużyć w bloku catch skojarzonym z zewnętrznym blokiem try. Ponowne zgłoszenie wyjątku odbywa się przy pomocy samego słowa throw.

try
{
...
}
catch(Exception ex)
{
...
throw ; //ponowne zgłoszenie wyjątku
}

0x08 graphic
Pytania sprawdzające

  1. Popraw błąd logiczny w poniższym przykładzie:

    if(x < 0) ;
    Console.WriteLine("x jest liczbą ujemną");

    Odp.
    Z kontekstu wynika że poleceni
    Console.WriteLine("x jest liczbą ujemną");
    powinno zależeć od warunku if. Bezpośrednio po instrukcji if nie powinno być średnika.

    if(x < 0)
    Console.WriteLine("x jest liczbą ujemną");

  2. Napisz fragment kodu, który sprawdza, czy zmienna x jest parzysta i wypisuje odpowiedni komunikat o tym na ekranie.

    Odp.
    if(x % 2 == 0)
    Console.WriteLine("x jest liczbą parzystą");

  3. Napisz fragment kodu, który sprawdza czy zmienna p oznaczająca prawdopodobieństwo jest spoza przedziału <0,1>. Jeżeli jest, zgłoś wyjątek ArgumentException.

    Odp.
    if(p < 0 || p > 1)
    throw new ArgumentException("Wartość
    ¬parametru p musi być z przedziału <0; 1>!");

  4. Dlaczego w poniższym kodzie zgłaszany jest błąd kompilatora?

    try
    {
    ...
    }
    catch (Exception ex) {...}
    catch (IOException) {...}

    Odp.
    W powyższy kodzie jest błąd, ponieważ instrukcja catch obsługująca bardziej ogólny typ wyjątku, występuje przed instrukcją catch z typem wyjątku bardziej szczegółowym.

0x08 graphic
Laboratorium

Ćwiczenie 1
Utwórz programu, który obliczy pierwiastki równania kwadratowego:
ax2+bx+c=0.

  1. Uruchom Visual Studio
    Naciśnij przycisk Start systemu Windows, wybierz Wszystkie Programy następnie Microsoft Visual Studio 2005/ Microsoft Visual Studio 2005.

  2. Utwórz nowy projekt

    1. Z menu File wybierz New/Project...

    2. W oknie dialogowym New Project określ następujące właściwości:

      1. typu projektu: Visual C#/Windows

      2. szablon: Console Application

      3. lokalizacja: Moje Dokumenty\Visual Studio 2005\Projects\

      4. nazwa projektu: Delta.

      5. nazwa rozwiązania: Lab3

  3. Wewnątrz metody Main napisz następujący kod:

    1. Zadeklaruj następujące zmienne rzeczywiste a, b, c, x1, x2, delta. Zmienna a, b, są parametrami równania kwadratowego, w zmiennych x1, x2 będziemy przechowywać wartości pierwiastków, zmienna delta jest zmienną pomocniczą:

      double a, b, c, x1, x2, delta;

    2. Ustaw blok try catch. W bloku catch przechwyć wszystkie wyjątki i wypisz wiadomość związaną z zgłoszonym wyjątkiem:

      try
      {
      ...
      }
      catch(Exception ex)
      {
      Console.WriteLine("Program został przerwany.
      ¬{0}", ex.Message);
      }

    3. Po bloku catch zatrzymaj program, aby użytkownik mógł obejrzeć wyniki.

      ...
      Console.ReadKey();

Dalsza część kodu będzie umieszczona w bloku try

    1. Pobierz od użytkownika wartość parametru a. Jeżeli wartość jest równa zero zgłoś wyjątek.

      ...
      Console.Write("Podaj wartość parametru a: ");
      a = Convert.ToDouble(Console.ReadLine());
      if(a == 0)
      {
      throw new Exception("Parametr a powinien być
      ¬różny od zera");
      }

    2. Pobierz od użytkownika wartości parametrów b i c:

      ...
      Console.Write("Podaj wartość parametru b: ");
      b = Convert.ToDouble(Console.ReadLine());
      Console.Write("Podaj wartość parametru c: ");
      c = Convert.ToDouble(Console.ReadLine());

    3. Oblicz wartość parametru delta dla podanych parametrów.

      delta = b * b - 4* a * c;

    4. Jeżeli delta jest większa od zera oblicz oba pierwiastki i wypisz ich wartości na ekranie.

      ...
      if(delta > 0)
      {
      x1 = (-b - Math.Sqrt(delta))/(2*a);
      x2 = (-b + Math.Sqrt(delta))/(2*a);
      Console.WriteLine("Równanie ma dwa
      ¬pierwiastki:");
      Console.WriteLine("\tx1 = {0}", x1);
      Console.WriteLine("\tx2 = {0}", x2);
      }

    5. W przeciwnym razie sprawdź czy delta jest równa 0 i oblicz pojedynczy pierwiastek. Jeżeli nie, wypisz, że równanie nie ma pierwiastków rzeczywistych

      ...
      else
      {
      if(delta == 0)
      {
      x1 = -b/(2*a);
      Console.WriteLine("Równanie ma jeden
      ¬pierwiastek rzeczywisty:");
      Console.WriteLine("\tx1 = {0}", x1);
      }
      else
      {
      Console.WriteLine("Równanie nie ma
      ¬pierwiastków rzeczywistych.");
      }
      }

  1. Skompiluj i uruchom program.

Ćwiczenie 2
Napisz program, który oblicza wartość wyrażenia:

0x08 graphic


  1. Dodaj do bieżącego rozwiązania nowy projekt

    1. Z menu File wybierz Add/New Project...

    2. W oknie dialogowym Add New Project określ następujące właściwości:

      1. typu projektu: Visual C#/Windows

      2. szablon: Console Application

      3. nazwa projektu: Wyrazenie.

  2. Uczyń nowo utworzony projekt projektem startowym

    1. Zaznacz projekt Wyrazenie w okienku Solution Explorer i z menu kontekstowego wybierz Set as StartUp Project.

albo, gdy rozpoczynasz laboratorium od tego ćwiczenia

  1. Uruchom Visual Studio
    Naciśnij przycisk Start systemu Windows, wybierz Wszystkie Programy następnie Microsoft Visual Studio 2005/ Microsoft Visual Studio 2005.

  2. Utwórz nowy projekt

    1. Z menu File wybierz New/Project...

    2. W oknie dialogowym New Project określ następujące właściwości:

      1. typu projektu: Visual C#/Windows

      2. szablon: Console Application

      3. lokalizacja: Moje Dokumenty\Visual Studio 2005\Projects\

      4. nazwa projektu: Wyrazenie.

      5. nazwa rozwiązania: Lab3

  1. Wewnątrz metody Main napisz następujący kod:

    1. Zadeklaruj cztery zmienne rzeczywiste x, y, z, v.

      double x,y,z;

    2. Pobierz od użytkownika wartości zmiennych x, y:

      Console.Write("Podaj wartość zmiennej x: ");
      x = Convert.ToDouble(Console.ReadLine());
      Console.Write("Podaj wartość zmiennej y: ");
      y = Convert.ToDouble(Console.ReadLine());

    3. Sprawdź czy zmienna x i zmienna y nie są mniejsze od zera. Jeżeli są, do zmiennej z wstaw wartość iloczyny xy.

      if(x < 0 && y < 0)
      {
      z = x * y;
      }

    4. W przeciwnym wypadku sprawdź czy zmienna x lub zmienna y ma wartość zero. Gdy tak jest, do zmiennej z wstaw wartość 10, natomiast w przeciwnym razie do zmiennej z wstaw sumę x + y.

      else
      {
      if(x == 0 || y == 0)
      {
      z = 10;
      }
      else
      {
      z = x + y;
      }
      }

    5. Wypisz wartość zmiennej z, a następnie zatrzymaj program, aby użytkownik mógł obejrzeć wyniki.

      Console.Write(" z = {0}" , z);
      Console.ReadKey();

  2. Skompiluj i uruchom program.0x01 graphic

1

P1

0x01 graphic

0x01 graphic

P2

L

L



Wyszukiwarka

Podobne podstrony:
Modul2, Courseware Development Tools
Modul1, Courseware Development Tools
Modul1, Courseware Development Tools
Modul9, Courseware Development Tools
Modul7, Courseware Development Tools
Modul8, Courseware Development Tools
Modul6, Courseware Development Tools
Modul12, Courseware Development Tools
4 ABAP Development Tools
GameBoy Development Tools
101 Learning and Development Tools Rok wydania 2011 oprawa miekka
Developing Usability Tools And Techniques For Designing And Testing Web Sites
A Grammar Development Course
A Grammar Development Course
Course hydro pl 1
4 Plant Structure, Growth and Development, before ppt
Marketing Management Course
Human Development Index

więcej podobnych podstron