Pozostałe instrukcje sterujące
Przegląd zagadnień
W module tym poznamy pozostałe instrukcje sterujące. Zostanie omówiona
instrukcja wielokrotnego wyboru switch oraz instrukcje skoku.
Zaprezentowany zostanie też pewien szablon programu.
Po skończeniu tego modułu studenci powinni umieć wykorzystywać
następujące instrukcje:
switch
goto
break
continue
Instrukcja wyboru switch
Przy omawianiu instrukcji wyboru if zostało wspomniane o wyborze
wielowariantowym. Jako wybór wielowariantowy bardziej od instrukcji if,
nadaje się instrukcja switch. Ogólna postać instrukcji switch jest
następująca:
switch(wyrazenie)
//nie ma średnika
{
case wartoscA: instrA1;
...;
instrAn;
break;
...
case wartoscN: instrN1;
...;
instrNn;
break;
default : instr1;
...;
instrn;
break;
}
Wartość wyrażenia wyrazenie musi być typu całkowitego (sbyte,
byte, short, ushort, int, uint, long, ulong, char) lub
dowolnego typu wyliczeniowego lub typu string lub typu, z którego istnieje
konwersja niejawna do jednego z podanych wcześniej typów. Wartości stojące
obok słowa case (wartoscA ... wartoscN) muszą być znane w czasie
kompilacji, czyli mogą być to literały lub stałe nazwane .Wartości te nie mogą
się powtarzać. Typ wartości stojących obok słowa case musi być zgodny z
typem wyrażenia wyrazenie.
Działanie instrukcji switch można przedstawić w kilku krokach:
1. Obliczana jest wartość wyrażenia wyrazenie.
2. Jeżeli któraś wartość stojąca obok słowa case jest równa obliczonej
wartości, to wykonywane są instrukcje danego bloku case. Po
osiągnięciu instrukcji break następuje wyjście z bloku instrukcji
switch.
3. Jeżeli żadna wartość stojąca obok słowa case, nie odpowiada
obliczonej wartości wyrażenia wyrazenie, wykonywane są
instrukcje bloku default. Po osiągnięciu instrukcji break następuje
wyjście z bloku instrukcji switch.
4. Jeżeli żadna wartość stojąca obok słowa case, nie odpowiada
obliczonej wartości wyrażenia wyrazenie i w bloku instrukcji
switch nie ma instrukcji default, to następuje wyjście z bloku
instrukcji switch.
Blok default może wystąpić w bloku instrukcji switch najwyżej jeden
raz.
Jeżeli chcemy, aby dane instrukcje były wykonywane dla kilku wartości
wyrażenia wyrazenie, należy postąpić jak w poniższym przykładzie, który
oblicza liczbę dni w miesiącu, nie uwzględniając lat przestępnych. Zmienna
miesiac zawiera numer miesiąca w roku.
int liczbaDni = 0;
switch(miesiac)
{
case 2 : liczbaDni = 28;
break;
case 4 :
case 6 :
case 9 :
case 11 : liczbaDni = 30;
break;
default : liczbaDni = 31;
break;
}
Trzeba pamiętać, że jeśli w bloku case lub default występuje jakaś
instrukcja, to przed rozpoczęciem nowego bloku case, defaut lub nawiasem
klamrowym zamykającym blok instrukcji switch, musi wystąpić instrukcja
skoku: break, goto, throw oraz return. Instrukcja goto zostanie
omówiona w dalszej części tego rozdziału. Brak instrukcji skoku jest nazywany
błędem przejścia (no fall through rule). Wyjątkiem od tej reguły jest sytuacja,
gdy po case występuje pętla nieskończona. Reguła ta różni język C# np. od
języków C/C++. Pozwolenie na przejście między blokami case było
przyczyną licznych błędów logicznych w programach napisanych w C/C++.
Poniższy kod spowoduje więc błąd kompilatora.
switch(c)
{
case 'a': instrA1;
//brak instrukcji skoku
case 'A': instrukcjaA1;
instrukcjaA2;
...
break;
case 'b': instrB1; //brak instrukcji skoku
case 'B': instrukcjaB1;
instrukcjaB2;
...
break;
default: instrukacja1;
//brak instrukcji skoku
}
Rozwiązanie problemu, gdy jest potrzeba uruchomienia kodu zawartego w inny
bloku case, po kodzie w danym bloku case, jest przedstawione przy
omawianiu instrukcji goto w tym module.
goto
Instrukcja goto jest instrukcją o "złej sławie". Używanie instrukcji goto
powoduje, że program staje się nieczytelny. Powinniśmy się, więc starać jej
unikać. Instrukcja goto ma następującą formę:
goto etykieta;
Po napotkaniu takiej instrukcji wykonywanie programu przenosi się do miejsca
gdzie jest zdefiniowana dana etykieta. Definicja etykiety składa się z jej nazwy
i znaku dwukropka.
etykieta :
Etykieta może mieć następujące formy:
identyfikator
case wartosc
default
W przypadku dwóch ostatnich rodzajów etykiet, instrukcja goto musi się
znajdować w bloku instrukcji switch. W przypadku pierwszym, goto może
skoczyć do etykiety na zewnątrz bloku lub do etykiety w tym samym bloku.
Nie może skoczyć do etykiety, która jest zdefiniowana w bloku
zagnieżdżonym, wewnętrznym w stosunku do bloku instrukcji goto.
Instrukcja goto nie może występować w bloku finally.
Uwaga:
Instrukcja goto nie może przenieść wykonania kodu z jednej metody do
drugie - nie może "skakać" między metodami
Etykieta w bloku
zewnętrznym - OK
Etykieta w tym samym
bloku - OK
Etykieta w bloku
wewnętrznym - źle
et:
...
{
...
goto et;
...
}
goto et;
...
et:
...
goto et;
...
{
...
et:
...
}
W przypadku, gdy etykieta nie jest dostępna, podczas kompilacji zgłaszany jest
błąd
Mimo złej sławy są dwie sytuacje, kiedy instrukcja goto jest na pewno
przydatna.
1. Natychmiastowe opuszczenie wielokrotnie zagnieżdżonych pętli.
while(...){
...
for(...){
...
while(...){
...
goto Etykieta;
}
}
}
Etykieta:
instrukcja1;
2. W celu ominięcia reguły zabraniającego przejścia między blokami
case lub blokiem default (no fall through rule).
switch(c)
{
case 'a':instrA1;
goto case 'A';
case 'A':instrukcjaA1;
instrukcjaA2;
...
break;
case 'b': instrB1;
goto default;
default: instrukacja1;
break;
}
break
Poznaliśmy już wcześniej działanie instrukcji break - polegało na przerwaniu
wykonywania instrukcji switch. Oprócz tego break może również
przerywać działanie pętli. Jeśli jest kilka pętli zagnieżdżonych jedna w drugiej,
to instrukcja break powoduje przerwanie tylko tej pętli, w której bezpośrednio
się znajduje - patrz slajd.
continue
Instrukcja continue może występować wewnątrz bloku instrukcji
iteracyjnych. W odróżnieniu od instrukcji break, instrukcja continue nie
kończy działania pętli, ale przerywa tylko aktualny obieg pętli i zaczyna
następny, kontynuując pracę pętli - patrz slajd. Podobnie jak break, w
przypadku pętli zagnieżdżonych, instrukcja continue przerywa aktualny
obieg tej pętli, w której bezpośrednio się znajduje.
Szablon programu
Używając poznane do tej pory instrukcje, można przedstawić pewien szablon
programu. Szablon możemy przedstawić przy pomocy następującego schematu:
Przykładową implementację można obejrzeć na slajdzie. Bardziej rozbudowany
przykład jest dostępny projekcie SzablonProjektu, który jest częścią
rozwiązania Kurs\Demo\Modul5\Modul5.sln, gdzie Kurs jest
katalogiem gdzie skopiowano pliki kursu.
Wyświetlenie menu, czyli
co program robi i jak go
do tego zmusić.
Oczekiwanie na polecenie
od użytkownika i jego
pobranie.
Przetworzenie polecenia
użytkownika i
wyświetlenie wyników.
Uwaga:
W programie wykorzystywane są metody i właściwości klasy Console, które
do tej pory nie były omówione:
Console.BufferHeight - liczba dostępnych wierszy w okienku..
Console.WindowHeight - liczba widocznych wierszy w oknie.
Musi być ona mniejsza od właściwości
Console.LargestWindowHeight
, która oznacza maksymalną
ilość wierszy, wyznaczoną w oparciu o bieżącą wielkość fontu i
rozdzielczość ekranu.
Console.BufferWidth - liczba dostępnych kolumn w okienku.
Musi być ona mniejsza od właściwości
Console.LargestWindowWidth
, która oznacza maksymalną
ilość kolumn, wyznaczoną w oparciu o bieżącą wielkość fontu i
rozdzielczość ekranu.
Console.WindowWidth - liczba widocznych kolumn w oknie.
Console.BackgroundColor - kolor tła.
Console.ForegroundColor - kolor czcionki.
Console.Clear() - wyczyszczenie całego okna, zamazanie
kolorem tła.
Console.CursorLeft - numer kolumny, gdzie znajduje się
kursor. Kolumny są numerowane od lewej do prawej strony okna.
Console.CursorTop - numer wiersza, gdzie znajduje się kursor.
Wiersze są numerowane od góry do dołu okna.
Console.SetCursorPosition(kolumna, wiersz) -
ustawienie pozycji kursora, czyli gdzie będzie rozpoczynać się
operacja wypisania na ekranie
Console.ReadKey(true).KeyChar - wartość typu char
naciśniętego klawisza.
Warto wspomnieć jeszcze o jednej metodzie klasy Console:
Console.Beep(czestotlowosc, czas) - wydanie dźwięku o
danej częstotliwość podanej w Hz, przez określony czas podany w ms.
Pytania sprawdzające
1. Jakie błędy popełniono w poniższym kodzie:
double x;
...
switch(x){
case 4 :
case 4 : instrukcjaA1;
break;
default: instrukacj2;
}
Odp.
W powyższym kozie popełniono następujące błędy:
- wartość (zmienna ) typu double nie może być wartością przełączającą w
instrukcji switch. Dopuszczalne typy to wszystkie typy całkowite, typ
string oraz typy wyliczeniowe,
- powtórzona wartość 4. Każda wartość obok case musi być unikalna, w
danej instrukcji switch
- po etykiecie default występuje instrukcja, nie ma natomiast instrukcji
skoku break albo goto.
2. Jaka jest różnica między instrukcjami break i continue wewnątrz pętli?
Odp.
Instrukcja break powoduje przerwanie (zakończenie) pętli, natomiast
instrukcja continue kończy pojedynczy obieg pętli i zaczyna następny,
kontynuując pracę pętli.
Laboratorium
Ćwiczenie
Napisz program obliczający całkę oznaczoną z funkcji nieujemnej i całkowalnej
na zadanym przedziale np. f(x) = x
4
+ x
3
+ 2. Użytkownik ma do
wyboru:
metodę (metoda prostokątów lub metoda trapezów)
przedział całkowania
liczbę podziałów (podprzedziałów)
Całkę oznaczaną w przedziale <a; b> z funkcji nieujemnej całkowalnej na
przedziale <a, b> można interpretować jako pole obszaru ograniczonego
wykresem tej funkcji, osią OX oraz prostymi x = a i x = b.
W przypadku metody prostokątów, pole to przybliżamy, sumą pól odpowiednio
dobranych prostokątów. Sposób postępowania jest następujący: Przedział
całkowania <a; b> dzielimy na n równych podprzedziałów: <x
0
; x
1
>,
<x
1
; x
2
>, ..., <x
n-2
; x
n-1
>, <x
n-1
; x
n
>
gdzie:
Dla każdego punktu x
i
, gdzie i = 1...n, wyznaczamy wartość funkcji w tym
punkcie. Będzie to długość jednego z boków prostokąta. Długość drugiego
boku jest równa szerokości podprzedziału. Sumę pól prostokątów, a więc
przybliżoną wartość całki, możemy zapisać wzorem:
W przypadku metody trapezów, zamiast sumy pól prostokątów używamy sumę
pól trapezów:
Pole trapezu jest równe iloczynowi wysokości i połowy sumy podstaw trapezu.
Dla podprzedziału <x
i-1
; x
i
> pole trapezu można zapisać:
Wzór na przybliżoną wartość całki, jako sumę pól trapezów, można więc
zapisać:
Dzięki powyższemu przekształceniu podczas obliczania wartości sumy, f(x
i
)
będzie obliczane tylko raz zamiast dwóch razy, gdybyśmy zastosowali wzór bez
przekształceń.
n
i
n
b
a
i
a
x
b
x
a
x
i
n
,...,
0
,
,
0
n
i
i
b
a
n
i
i
x
f
n
a
b
n
a
b
x
f
dx
x
f
1
1
)
(
)
(
)
(
)
(
2
1
2
1
podst
podst
h
P
trapezu
)]
(
)
(
[
2
1
1
i
i
trapezu
x
f
x
f
n
b
a
P
1
1
1
1
1
1
)
(
2
)
(
)
(
)]
(
)
(
[
2
2
)
(
)
(
)
(
n
i
i
n
i
i
i
b
a
n
i
i
i
x
f
b
f
a
f
n
a
b
x
f
x
f
n
a
b
n
a
b
x
f
x
f
dx
x
f
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
a. Z menu File wybierz New/Project...
b. W oknie dialogowym New Project określ następujące właściwości:
i. typu projektu: Visual C#/Windows
ii. szablon: Console Application
iii. lokalizacja: Moje Dokumenty\Visual Studio 2005\Projects\
iv. nazwa projektu: Calki.
v. nazwa rozwiązania: Lab5
3.
Przed metodą Main zdefiniuj nowy typ wyliczeniowy o nazwie
MetodyCalkowania
, który ma dwa elementy Prostokatow=1 oraz
Trapezow=2
enum MetodyCalkowania { Prostokatow=1, Trapezow=2 }
4. Wewnątrz metody Main napisz następujący kod:
a. Zadeklaruj następujące zmienne i nadaj im wartości początkowe:
i. dwie zmienne rzeczywiste a i b, reprezentujące początek i koniec
przedziału całkowania. a musi być mniejsze od b.
ii. Zmienną dodatnią n reprezentującą liczbę podziałów przedziału
całkowania.
iii. Zmienną metoda typu MetodyCalkowania.
double a = 0, b = 1;
uint n = 10;
MetodyCalkowania metoda =
MetodyCalkowania.Prostokatow;
b.
Ustaw główną pętlę programu:
char c;
do
{
...
}
while(!(c =='K' || c=='k'));
c. W głównej pętli programu:
i. Wyświetl menu programu. Menu powinno zawierać informacje na
temat: aktualnego przedziału całkowania, ilości podziałów i
wybranej metody całkowania oraz jak zmienić przedział
całkowania, metodę całkowania, liczbę podziałów, jak obliczyć
całkę dla podanych parametrów i jak zakończyć program.
Console.WriteLine("Przedział całkowania: <{0}; {1}>",
a, b);
Console.WriteLine("Liczba podziałów: {0}", n);
Console.WriteLine("Metoda całkowania: {0}", metoda);
Console.WriteLine("-----");
Console.WriteLine("A - Zmiana przedziału");
Console.WriteLine("B - Zmiana liczby podziałów");
Console.WriteLine("C - Zmiana metody całkowania");
Console.WriteLine("D - Policz całkę");
Console.WriteLine("K - Koniec");
ii. Pobierz od użytkownika polecenie:
c = Console.ReadKey(true).KeyChar;
iii. Ustaw blok instrukcji switch w celu przetworzenia poleceń użytkownika:
switch(c)
{
...
}
iv. W bloku switch napisz następujący kod przetwarzający polecenia
użytkownika:
1.
Obsługa zmiany przedziału całkowania:
Pobierz wartość początku przedziału i wstaw do zmiennej a. Pobierz
wartość końca przedziału i wstaw do zmiennej b. Dopilnuj, aby
wartość b była większa od a:
case 'a':
case 'A':
do
{
if (a > b)
{
Console.Write("Początek musi być
¬mniejszy od końca: ");
}
Console.Write("Podaj początek przedziału: ");
a = Convert.ToDouble(Console.ReadLine());
Console.Write("Podaj koniec przedziału: ");
b = Convert.ToDouble(Console.ReadLine());
}
while (a > b);
break;
2. Obsługa zmiany liczby podziałów:
Pobierz wartość liczby podziałów i wstaw do zmiennej n. Jeżeli
użytkownik podał zero, zgłoś wyjątek.
case 'b':
case 'B':
Console.Write("Podaj liczbę podziałów: ");
n = Convert.ToUInt32(Console.ReadLine());
if (n == 0)
throw new Exception("Liczba podziałów musi
¬ być większa od zera");
break;
3. Obsługa zmiany metody całkowania:
Pobierz metodę całkowania od użytkonika:
case 'c':
case 'C':
int m = 1;
do
{
if (m != 1)
{
Console.Write("Naciśnij 1 lub 2: ");
}
Console.WriteLine("Podaj metodę liczenia
¬całki: ");
Console. WriteLine("\t1-Metoda prostokątów");
Console. WriteLine ("\t2 - Metoda trapezów: ");
m = Convert.ToInt32(Console.ReadLine());
}
while (!(m == 1 || m == 2));
metoda = (MetodyCalkowania)m;
break;
4.
Obsługa liczenia całki
a. Dodaj nowy blok case:
case 'd':
case 'D':
b. Zadeklaruj trzy pomocnicze zmienne rzeczywiste: suma i nadaj
jej wartość zero, dx (szerokość podprzedziału) i nadaj jej wartość
(b-a)/n, x i nadaj jej wartość a.
double suma = 0;
double dx = (b - a) / n;
double x = a;
c. W zależności od wartości zmiennej metoda, oblicz wartość całki i
wstaw ją do zmiennej suma. Jeżeli wartość zmiennej metoda jest
równa MetodyCalkowania.Prostokatow skorzystaj ze
wzoru:
Jeżeli wartość zmiennej metoda jest równa
MetodyCalkowania.Trapezow skorzystaj ze wzoru:
Uwaga:
W celu optymalizacji obliczania wartości funkcji w punkcie,
możemy skorzystać z następującego przekształcenia:
f(x) = x
4
+x
3
+2 = x
3
(x+1)+2
switch (metoda)
n
i
i
b
a
x
f
n
a
b
dx
x
f
1
)
(
)
(
1
1
)
(
2
)
(
)
(
)
(
n
i
i
b
a
x
f
b
f
a
f
n
a
b
dx
x
f
{
case MetodyCalkowania.Prostokatow:
for (int i = 0; i < n; i++)
{
x += dx;
suma += x * x * x * (x + 1) + 2;
}
suma *= dx;
break;
case MetodyCalkowania.Trapezow:
for (int i = 1; i < n; i++)
{
x += dx;
suma += x * x * x * (x + 1) + 2;
}
suma += (a * a * a * (a + 1) +
b * b * b * (b + 1) + 4) / 2;
suma *= dx;
break;
}
d.
Wypisz wartość całki (sumy) na ekranie i poczekaj na reakcję
użytkownika, aby mógł obejrzeć wynik.
Console.Write("Przybliżona wartość całki
¬funkcji f(x) w przedziale <{0}; {1}>
¬ wynosi: {2}",a, b, suma);
Console.ReadKey(true);
break;
5. Skompiluj i uruchom program.