background image

VI.

Procedury i funkcje – metody

Język Visual Basic 2010 umożliwia programiście utworzenie ciągu instrukcji i nadanie mu na-

zwy. Możliwość ta jest odzwierciedleniem praktyki życiowej polegającej na nadawaniu pewnemu cią-

gowi czynności nazwy, która jednoznacznie opisuje te czynności oraz ich kolejność. Przykładami ta-

kich nazw są: odkurzanie, skakanie ze spadochronem, smażenie jajecznicy itp. Drugi przykład ma tu 

szczególne znaczenie, gdyż wiadomo, że pominięcie którejkolwiek z czynności w procedurze skaka-

nia ze spadochronem może zakończyć się tragicznie.

Słowem kluczowym poprzedniego akapitu jest „procedura”. Określa ono wydzielony z programu 

ciąg instrukcji, któremu nadano nazwę. Nazwa służy do uruchamiania takiego ciągu instrukcji (czyli  

procedury) w dowolnej części programu. Visual Basic 2010 pozwala na tworzenie kilku typów proce-

dur. Bieżący rozdział omawia jedynie dwa z nich – podprogramy i funkcje – określane wspólnym mia-

nem metod.

VI.1.

Podprogramy – metody typu Sub

Załóżmy, że programista chciałby, aby każdy jego program rozpoczynał swoją pracę od przed-

stawienia pewnych danych.

Przykład.

********************************************
*                                          *
*     Program wykonał: Imię Nazwisko       *
*     adres e-mail: konto@serwer           *
*                                          *
********************************************

Naciśnij Enter, aby kontynuować...

Napisanie kodu źródłowego, który wykona taką winietę nie nastręcza żadnych kłopotów – wy-

starczy użyć instrukcji WriteLine i ReadLine konsoli. Jednak pisanie takiego kodu, a nawet kopiowa-

nie go wymaga czasu. Kolejne ćwiczenie będzie polegać na napisaniu kodu realizującego przedsta -

wienie danych o autorze projektu.

Ćwiczenie 60
Utworzyć Projekt_060 aplikacji konsolowej, która przedstawi dane autora aplikacji w 
sposób podobny do przedstawionego w przykładzie powyżej (użyć własnych danych – 
imię, nazwisko i e-mail).

Aplikacja wykonuje winietę przedstawiając dane autora projektu.

background image

Aby uniknąć ciągłego przepisywania instrukcji kodu w kolejnym ćwiczeniu utworzony zostanie 

podprogram – procedura typu Sub, której podstawowa postać wygląda następująco:

Sub NazwaPodprogramu()

‘Instrukcje

End Sub

Ćwiczenie 61
Dodać do Projekt_060 moduł o nazwie Moje_060 i zdefiniować w nim procedurę Sub o 
nazwie Winieta, w której bloku znajdą się instrukcje przeniesione z procedury Main.

1. Dodaj do Projekt_060 moduł Moje_060 (wykorzystaj schemat wprowadzony w ćwiczeniu 

18.).

2. Utwórz w dodanym module szkielet procedury Winieta, w tym celu:

wpisz słowo kluczowe Sub, rozpoczynające deklarację podprogramu i dopisz spa-

cję,

wpisz nazwę procedury – Winieta i naciśnij Enter. Zauważ, że edytor wspomaga 

tworzenie procedur, dodając po nazwie parę nawiasów okrągłych oraz słowa klu-

czowe End Sub kończące blok podprogramu. Kursor tekstowy znajduje się we-

wnątrz bloku.

3. Przenieś wszystkie instrukcje z procedury Main do procedury Winieta.

4. Sprawdź działanie aplikacji.

Aplikacja nie wykonuje winiety, gdyż podprogram nie został uruchomiony. Istnieją dwa sposoby 

uruchamiania podprogramów. Pierwszy, tradycyjny, polega na użyciu słowa kluczowego Call (Wołaj, 

przywołaj), a po nim nazwy podprogramu.

Przykład.

Call Podprogram()

Druga,  najczęściej  współcześnie  stosowana,  polega  na  użyciu  nazwy  podprogramu  tak,  jak 

zwykłej instrukcji języka.

Przykład.

Podprogram()

Kolejne ćwiczenie polegać będzie na wywołaniu podprogramu Winieta w procedurze Main.

Ćwiczenie 62
Uruchomić podprogram Winieta w procedurze Main.

1. Wpisz w procedurze Main instrukcję Winieta().

background image

2. Sprawdź działanie aplikacji.

Każdy projekt, do którego programista dołączy moduł Moje_060 będzie mógł wykorzystać pro-

cedurę Winieta. Obrazuje to kolejne ćwiczenie.

Ćwiczenie 63
Utworzyć Projekt_063 aplikacji konsolowej. Dodać do niego moduł Moje_060 i wywołać 
w procedurze Main podprogram Winieta. Sprawdzić działanie aplikacji.

1. Utwórz nowy projekt aplikacji konsolowej.

2. Dodaj do projektu moduł Moje_060,  w tym celu:

wykorzystaj   polecenie  Add   Existing   Item   (Dodaj   istniejący  element)   zawarte   w 

menu Project (polecenie dostępne także w grupie poleceń Add menu podręczne-

go projektu w oknie Solution Explorer); polecenie posiada skrót klawiaturowy – 

Ctrl+D;   otwiera   ono   okno  Add   Existing   Item   będące   kopią   systemowego   okna 

Otwieranie,

odszukaj folder Projekt_060, a w nim plik modułu Moje_060 i zaznacz go,

zatwierdź wybór przyciskiem Add.

3. Zauważ, że w oknie Solution Explorer pojawił się plik Moje_060.vb (jest to kopia pliku za -

wartego w Projekt_060).

4. Sprawdź kod źródłowy modułu Moje_060.

5. Wywołaj w procedurze Main procedurę Winieta.

6. Sprawdź działanie aplikacji.

Aplikacja Projekt_063 „przedstawia się” identycznie jak aplikacja Projekt_060. Tworzenie kopii 

pliku pozwala programistom edytować moduły niezależnie.

Jeśli programista otrzyma nowy adres e-mail (np. po zmianie miejsca pracy) , to będzie zmu -

szony do edycji procedury Winieta. Podobnie będzie przy zmianie nazwiska (współcześnie, zjawi-

sko to, dotyczy tak kobiet, jak i mężczyzn). Problem ten rozwiązuje się poprzez zdefiniowanie para-

metrów podprogramu.

Parametry pozwalają na przekazanie do podprogramu wartości wpływających na sposób wyko-

nania podprogramu. Rzeczywistym przykładem może być obliczanie kwadratu liczby. Wiadomo, że 

aby obliczyć kwadrat liczby, należy pomnożyć ją przez taką samą wartość. Przykładowo, 2

2

=2*2. 

W matematyce zapis ten uogólnia się podając wzór: x

2

=x*x. We wzorze tym, x jest parametrem (ar-

gumentem) funkcji kwadratowej. Znając ten wzór można obliczyć kwadrat dowolnej liczby.

background image

Parametry procedury

Miejscem   definiowania   parametrów   jest   wnętrze   pary   nawiasów   okrągłych   występujących 

po nazwie podprogramu. Parametry pozwalają przekazywać do podprogramu pewne dane w chwili 

jego wywołania. Podprogram, któremu przekazano dane poprzez parametry wykorzystuje je w trakcie 

działania. Istnieją dwa sposoby przekazywania danych do podprogramu:

przez wartość (ang. by value),

przez referencję (ang. by reference).

Objaśnienie różnicy pomiędzy tymi sposobami nastąpi w podpunkcie dotyczącym parametrów 

przekazywanych przez referencję, do tego czasu wykorzystywany będzie jedynie pierwszy z nich.

Parametry przekazywane przez wartość

Deklaracja parametru przekazywanego przez wartość wygląda następująco:

ByVal NazwaParametru As TypParametru

Słowo kluczowe 

ByVal

 (skrót od by value) można opuścić, gdyż jest to domyślny sposób prze-

kazywania parametrów i edytor doda je automatycznie. Jeśli programista definiuje więcej niż jeden 

parametr, to deklaracje rozdziela się przecinkiem.

Przykład.

ByVal NazwaParametru1 As TypParametru1, ByVal NazwaParametru2 As TypParametru2

Kolejne ćwiczenie będzie polegać na zdefiniowaniu dwóch parametrów w procedurze Winieta

posłużą one do przekazywania do procedury nazwiska i adresu e-mail.

Ćwiczenie 64
Zdefiniować   w   procedurze   Winieta   parametry   Nazwisko   i   Email   typu   String 
przekazywane przez wartość.

1. Umieść kursor tekstowy wewnątrz pary nawiasów występujących po słowie Winieta w 

module Moje_060.

2. Zdefiniuj parametr Nazwisko typu String przekazywany przez wartość, w tym celu:

wpisz słowo ByVal i dopisz spację,

wpisz nazwę parametru – Nazwisko i dopisz spację, 

określ typ parametru – As String.

Przykład.

ByVal Nazwisko As String

background image

3. Zdefiniuj drugi parametr – Email typu String, w tym celu:

wpisz przecinek po słowie String określającym typ parametru Nazwisko i dopisz spa-

cję,

nie wpisuj słowa kluczowego ByVal – automatycznie doda je edytor,

wpisz nazwę parametru – Email – dopisz spację i określ typ parametru.

4. Przenieś kursor tekstowy do innego miejsca kodu i zauważ, że słowo kluczowe ByVal zo-

stało dodane przed deklaracją parametru Email.

5. Spróbuj uruchomić aplikację.

6. Zamknij przyciskiem Nie (No) komunikat o błędach w trakcie kompilacji.

Wywołanie procedury musi spełniać wymagania postawione w jej deklaracji. Procedura Wini-

eta posiada dwa parametry i tyle musi również zawierać jej wywołanie. Edytor jest w tym zakresie 

niezwykle pomocny – w trakcie wpisywania do kodu źródłowego wywołania procedury „podpowiada” 

jakich wymaga ona parametrów. Obrazuje to kolejne ćwiczenie.

Ćwiczenie 65
Wprowadzić wartości parametrów do wywołania procedury Winieta.

1. Ustaw kursor tekstowy wewnątrz pary nawiasów okrągłych w wywołaniu podprogramu 

Winieta w procedurze Main.

2. Naciśnij klawisz Delete (Usuń) i zauważ, że edytor wyświetla informację o wymaganych 

parametrach podprogramu podając ich nazwy i typy. Jest to sytuacja analogiczna do tej, 

jaka nastąpiłaby podczas wprowadzania nazwy podprogramu po wprowadzeniu lewego 

nawiasu okrągłego.

Przykład.

3. Wpisz w cudzysłowie ciąg znaków "NoweNazwisko" – będzie to wartość parametru Na-

zwisko przekazana do podprogramu.

4. Wpisz przecinek, a po nim w cudzysłowie ciąg znaków "Nowy E-mail" – będzie to war-

tość parametru Email przekazana do podprogramu.

5. Sprawdź działanie aplikacji.

background image

Projekt został skompilowany, a aplikacja uruchomiona. Jednak w winiecie nie pojawiły się tek-

sty: NoweNazwisko i Nowy E-mail. Podprogram wprawdzie otrzymał poprzez parametry wspomniane 

łańcuchy znaków, ale z nich nie korzysta. Przed wykonaniem kolejnego ćwiczenia, które ten błąd na-

prawi należy powiedzieć, że z parametrów podprogramu można (wewnątrz bloku podprogramu) ko -

rzystać tak, jak ze zmiennych.

Ćwiczenie 66
Użyć wartości parametrów Nazwisko i Email w podprogramie Winieta.

1. Zmodyfikuj wywołanie procedury WriteLine wypisującej wiersz z nazwiskiem tak, aby wy-

korzystywała wartość parametru Nazwisko.

Przykład. Procedurę Lset (patrz: Funkcje działające na łańcuchach znaków, podrozdział II.9) 

użyto aby uwzględnić fakt, że nazwiska mogą mieć różną długość, co nie powinno mieć wpływu na 

wygląd ramki z gwiazdek.

Console.WriteLine("*     Program wykonał: Imię {0}*", LSet(Nazwisko, 15))

2. Zmodyfikuj wywołanie procedury WriteLine wypisującej wiersz z adresem e-mail tak, aby 

wykorzystywała wartość parametru Email (weź pod uwagę zmienną długość adresu).

3. Sprawdź działanie aplikacji.

Prześledźmy działanie programu. Aplikacja, po wywołaniu procedury Winieta, tworzy dla niej 

zmienne typu  

String

  o nazwach Nazwisko i Email, następnie oblicza wartości parametrów,  które 

w ogólności mogą być wyrażeniami, i przypisuje wyniki do utworzonych zmiennych. Kolejnym kro-

kiem jest wykonanie instrukcji podprogramu. Ponieważ zmienne posiadają wartości pobrane z ze -

wnątrz, to każde wywołanie podprogramu może dać inny efekt w zależności od wartości parametrów 

podanych w wywołaniu. Parametry podprogramu „żyją” tylko tak długo, jak długo wykonywany jest 

podprogram. Zmiana wartości parametru wewnątrz podprogramu nie wpływa zatem na zmienne zde-

finiowane na zewnątrz.

Wspomniano, że dane przekazywane przez wartość mogą być wyrażeniami tak, jak w poniż-

szym przykładzie (zakłada się, że zmienne strKonto i strSerwerPoczty są typu String i zawierają od -

powiednie łańcuchy tekstowe):

Winieta("Nowenazwisko", strKonto & "@" & strSerwerPoczty)

Opis działania aplikacji będzie identyczny z poprzednim. Ponownie aplikacja tworzy zmienne 

podprogramu – Nazwisko i Email – następnie pobiera wartość pierwszego parametru wywołania i 

przypisuje ją pierwszej zmiennej. Kolejnym krokiem jest obliczenie wartości wyrażenia: 

strKonto & "@" & strSerwerPoczty

background image

Obliczona wartość – łańcuch tekstowy, np. "konto123@serwer.w.pl" – zostaje przypisana zmien-

nej podprogramu o nazwie Email. Obie zmienne mogą być następnie wykorzystane w trakcie działa -

nia podprogramu.

Aby odróżnić parametry określone w deklaracji podprogramu od wartości (wyrażeń) podawa-

nych w instrukcji wywołującej podprogram, te pierwsze nazywa się parametrami formalnymi, a te dru-

gie parametrami aktualnymi.

Należy podkreślić jeszcze jeden ważny problem. Jeśli jako parametr wywołania podprogramu 

zostanie podana zmienna (zakłada się, że w procedurze Main zadeklarowano zmienne Nazwisko i 

Adres oraz nadano im odpowiednie wartości):

Winieta(Nazwisko, Adres)

to w dalszym ciągu, aplikacja tworzy nową zmienną Nazwisko (a także Email), która jest zmien-

ną podprogramu i nie jest tożsama ze zmienną Nazwisko zadeklarowaną w procedurze Main. Jedy-

ne co je łączy, to to, że wartość zmiennej Nazwisko z procedury Main zostanie pobrana i przypisana 

zmiennej   Nazwisko   z   podprogramu  Winieta.   Jakakolwiek   zmiana   wartości   zmiennej   Nazwisko 

w procedurze Winieta nie ma wpływu na wartość zmiennej Nazwisko w procedurze Main.

Zdarza się jednak, że zachodzi potrzeba, aby podprogram zmieniał wartość zmiennej, którą 

przekazano mu jako parametr. Zagadnienie to omawia kolejny podpunkt.

Parametry przekazywane przez referencję

Deklaracja parametru przekazywanego przez referencję wygląda następująco:

ByRef NazwaParametru As TypParametru

Słowa kluczowego 

ByRef

 (skrót od by reference) nie można opuścić, gdyż w takim przypadku 

edytor doda automatycznie słowo kluczowe 

ByVal

. Angielskie słowo reference oznacza wzmiankę, 

odesłanie (np. odsyłacz do literatury), wskazanie.

Jeśli w wywołaniu procedury, w miejscu parametru zdefiniowanego ze słowem 

ByRef

, progra-

mista umieści nazwę zmiennej, nazwę pola zmiennej typu strukturalnego, nazwę komórki tablicy, ge-

neralnie – nazwę każdego elementu, którego wartość można modyfikować w programie, to zmiany 

wartości parametru wewnątrz podprogramu będą miały wpływ na wartość tego elementu w progra-

mie.

W przypadku, gdy programista umieści wyrażenie, lub nazwę elementu, którego wartości nie 

można zmienić w programie, np. stałej, elementu enumeracji itp., możliwe będzie jedynie zmienianie 

wartości zmiennej w podprogramie tak, jak to miało miejsce dla parametrów przekazywanych przez 

wartość. Kolejne ćwiczenie zapoznaje z definiowaniem parametrów przekazywanych przez referen-

cję.

background image

Ćwiczenie 67
Zdefiniować   procedurę   PobierzLiczbę   o   jednym   parametrze   PobieranaLiczba   typu 
Integer przekazywanym przez referencję i dwóch parametrach Min i Maks typu Integer 
przekazywanych   przez   wartość.   Procedura   powinna   wyświetlać   zachętę   do 
wprowadzania liczby całkowitej z przedziału <Min, Maks> i nie powinna pozwalać na 
wprowadzenie   liczby   leżącej   na   zewnątrz   tego   przedziału   (wykorzystać   pętlę   z 
warunkiem sprawdzanym na końcu).

1. Dodaj pusty wiersz w module Moje_060 po słowach End Sub kończących procedurę Wi-

nieta. Treść procedury Winieta można zwinąć używając przełącznika zwiń/rozwiń.

2. Utwórz  szkielet  deklaracji procedury PobierzLiczbę wpisując  słowo  kluczowe  Sub,  po 

spacji nazwę procedury i naciskając Enter.

3. Zdefiniuj, wewnątrz pary nawiasów okrągłych dodanych przez edytor po nazwie procedu-

ry, parametr PobieranaLiczba typu Integer przekazywany przez referencję, w tym celu:

wpisz słowo kluczowe ByRef (nie wolno go pominąć) i dopisz spację,

wpisz nazwę parametru – PobieranaLiczba – i określ jego typ.

4. Zdefiniuj parametr Min typu Integer (nie musisz wpisywać słowa kluczowego ByVal).

5. Zdefiniuj parametr Maks typu Integer.

6. Zbuduj wewnątrz procedury pętlę Do … Loop z warunkiem sprawdzanym na końcu, pętla 

powinna   być   wykonywana   tak   długo,   aż   wartość   zmiennej   PobieranaLiczba   będzie 

w przedziale <Min, Maks>.

7. Zaprogramuj wewnątrz pętli wyświetlanie zachęty do wprowadzenia wartości z przedzia-

łu <Min, Maks> oraz pobieranie od użytkownika wartości i przypisywanie jej zmiennej Po-

bieranaLiczba.

8. Skompiluj projekt.

Zdefiniowana procedura może mieć następującą postać:

Tylko parametr PobieranaLiczba może posłużyć do przekazania wartości na zewnątrz procedu-

ry PobierzLiczbę. Aby użyć tej możliwości, jako pierwszy parametr wywołania musi wystąpić ele-

ment zmienny, np. zmienna programu, komórka tabeli itp. Kolejne ćwiczenie pokaże zmianę wartości 

zmiennej programu po wywołaniu procedury z parametrem przekazywanym przez referencję.

Rysunek 24: Jedna z możliwych postaci procedury PobierzLiczbę.

background image

Ćwiczenie 68
Użyć   parametru   przekazywanego   przez   referencję   do   zmiany   wartości   zmiennej   na 
zewnątrz procedury.

1. Zadeklaruj w procedurze Main zmienną intLiczba typu Integer o wartości początkowej 9.

2. Wywołaj procedurę PobierzLiczbę z parametrami: intLiczba, 10, 15.

3. Zaprogramuj wyświetlenie w konsoli wartości zmiennej intLiczba.

4. Sprawdź działanie aplikacji.

Aplikacja wyświetla winietę, pobiera liczbę z przedziału <10, 15> i wyświetla wartość zmiennej 

intLiczba, która nie wynosi 9. Działanie aplikacji jest odmienne od tego, które występuje dla parame -

trów przekazywanych przez wartość. Zmienna intLiczba zadeklarowana w procedurze Main zajmuje 

w pamięci operacyjnej określone miejsce (posiada adres). W chwili wywołania procedury Pobierz-

Liczbę (oprócz zmiennych Min i Max) tworzona jest zmienna PobieranaLiczba. Jest to zmienna re-

ferencyjna. Referuje ona (wskazuje, odwołuje się) do tego samego miejsca w pamięci operacyjnej, 

które zostało przydzielone w celu przechowywania wartości zmiennej intLiczba. Występuje zatem 

taka sytuacja, że dwie zmienne przechowują swoją wartość w tym samym miejscu pamięci operacyj-

nej. Jeśli wartość zmiennej PobieranaLiczba zmieni się, to jest to jednoznaczne ze zmianą wartości  

zmiennej intLiczba.

Procedura PobierzLiczbę służy do wprowadzania liczb z określonego przedziału i jest dosyć 

ogólna, gdyż pozwala podać granice przedziału jako parametry wywołania (Min, Maks). Załóżmy, że 

procedura ta ma zostać wykorzystana wielokrotnie w programie, i w 95% wywołań górne ogranicze -

nie – Maks –  wynosi 215. Jeśli wywołań jest 100, to programista będzie musiał wpisać 95 razy liczbę  

215 i 25 razy inną liczbę. Problem ten został rozwiązany przez parametry opcjonalne.

Parametry o wartości domyślnej – opcjonalne

Parametry o wartości domyślnej tak, jak zwykłe parametry służą do przekazywania pewnych 

wartości do procedury. Charakteryzują się tym, że jeśli programista nie określi, podczas wywołania 

procedury, wartości parametru – pominie go, to procedura nada zmiennej wartość domyślną określo-

ną w deklaracji procedury. W tym zakresie występuje ograniczenie, a mianowicie, parametry domyśl-

ne muszą występować na liście parametrów jako ostatnie. Deklaracja parametru opcjonalnego ma 

następującą postać:

Optional ByVal NazwaParametru As TypParametru = WartośćDomyślna

lub

Optional ByRef NazwaParametru As TypParametru = WartośćDomyślna

background image

Jeśli w wywołaniu procedury, w miejscu parametru NazwaParametru nie występuje wartość, to 

zmiennej NazwaParametru, w procedurze, zostanie przypisana wartość początkowa WartośćDomyśl-

na. Definiowanie parametru opcjonalnego przedstawia kolejne ćwiczenie.

Ćwiczenie 69
Ustalić wartość domyślną parametru Maks na 215.

1. Ustaw kursor tekstowy przed słowem kluczowym ByVal rozpoczynającym deklarację pa-

rametru Maks i dopisz słowo kluczowe Optional (opcjonalny), następnie ustaw kursor po 

określeniu typu parametru i dopisz operator przypisania i liczbę 215.

2. Skompiluj projekt.

Pominięcie parametru w chwili wywołania procedury należy traktować dosłownie. Załóżmy, że 

wywołanie procedury PobierzLiczbę ma postać:

PobierzLiczbę(intLiczba, 10, 15)

W wywołaniu tym parametr Maks ma wartość 15, pominięcie, oznacza usunięcie go z wywoła-

nia:

PobierzLiczbę(intLiczba, 10, )

Liczba parametrów opcjonalnych może być większa. W kolejnym ćwiczeniu ustala się wartość 

domyślną parametru Min na 0.

Ćwiczenie 70
Ustalić wartość domyślną parametru Min na 0.

Procedura PobierzLiczbę ma już dwa parametry domyślne. Parametry domyślne można po-

mijać pojedynczo, lub wszystkie, lub dowolną ich kombinację. Jeśli programista chce pominąć tylko 

parametr Min, a nadać wartość 100 parametrowi Maks, to musi to zrobić następująco:

PobierzLiczbę(intLiczba, , 100)

Przecinki wskazują miejsce pominiętego parametru. Przy tym, jeśli pomija się grupę parame-

trów leżących na końcu listy, to przecinków można nie wpisywać. Np. pomijając ostatni parametr 

można użyć wywołania:

PobierzLiczbę(intLiczba, 10)

a pomijając dwa ostatnie:

PobierzLiczbę(intLiczba)

Kolejne ćwiczeniu pokazuje różne wywołania z uwzględnieniem parametrów opcjonalnych.

background image

Ćwiczenie 71
Wykorzystać procedurę PobierzLiczbę i różne kombinacje jej parametrów opcjonalnych.

1. Skopiuj dwa wiersze: wywołanie procedury PobierzLiczbę i instrukcję wypisującą wartość 

intLiczba w konsoli.

2. Wklej skopiowane wiersze pięć razy na końcu procedury Main.

3. Usuń liczbę 15 z pierwszego wywołania (pozostaw przecinek po liczbie 10).

4. Usuń liczbę 15 oraz poprzedzający ją przecinek z drugiego wywołania.

5. Usuń liczbę 10 z trzeciego wywołania.

6. Usuń liczby 10 i 15 z czwartego wywołania (pozostaw przecinki).

7. Usuń liczby 10 i 15 oraz przecinki z piątego wywołania.

8. Sprawdź działanie aplikacji.

Procedura  PobierzLiczbę  służy do pobierania liczby całkowitej z określonego przedziału. 

Może się okazać, że aplikacja równie często (np. 150 razy) pobiera liczbę rzeczywistą z ograniczone-

go zakresu i programista chciałby użyć funkcji o takiej samej nazwie – PobierzLiczbę, ale do po-

bierania liczb rzeczywistych. Problem ten rozwiązuje mechanizm przeciążania.

Przeciążanie procedur

Każdą procedurę charakteryzuje jednoznacznie pierwszy wiersz jej deklaracji:

Sub Nazwa (Parametr1, Parametr2)

Nazwa procedury oraz liczba, typ i kolejność parametrów (bez opcjonalnych) stanowią zestaw 

informacji nazywany sygnaturą procedury. Dwie procedury są różne (to znaczy jednoznacznie rozpo-

znawalne przez kompilator) jeśli posiadają różne sygnatury. Oznacza to, że procedury są różne, jeśli 

ich sygnatury różnią się przynajmniej jednym elementem, np. typem trzeciego parametru, liczbą pa-

rametrów itd. Można zatem zbudować dwie procedury o identycznej nazwie, które będą jednak róż -

ne.

Opisany  mechanizm   nazywa   się   przeciążaniem,   a  zdefiniowane   w  ten   sposób   procedury  – 

przeciążonymi. Przeciążanie zilustruje kolejne ćwiczenie.

background image

Ćwiczenie 72
Utworzyć przeciążoną procedurę PobierzLiczbę, która będzie pobierać liczbę rzeczywistą 
z określonego przedziału.

1. Skopiuj deklarację procedury PobierzLiczbę i wklej kopię poniżej oryginału. Zauważ, że 

nazwa pierwszej „wersji” została podkreślona, a po naprowadzeniu na nią kursora myszy 

edytor podpowiada, że:

Public Sub PobierzLiczbę(ByRef PobieranaLiczba As Integer, [Min As Integer = 0], [Maks As In-

teger = 215])' has multiple definitions with identical signatures. – Publiczna procedura PobierzLiczbę  

posiada wiele deklaracji z identycznymi sygnaturami. 

2. Zmień typ wszystkich parametrów w drugim egzemplarzu procedury z Integer na Double. 

Zauważ, że po zmianie typu pierwszego parametru znika błąd sygnalizowany w punkcie 

1. ćwiczenia.

3. Zmień tekst zachęty tak, aby użytkownik wiedział, że może wprowadzić liczbę rzeczywi -

stą. 

4. Zmień funkcję konwersji CInt na Cdbl.

5. Skompiluj projekt.

Kompilator odróżnia od siebie dwie wersje procedury PobierzLiczbę. Programista może wy-

wołać każdą z nich używając tej samej nazwy. Jeśli użyje jako pierwszego parametru wielkości typu 

Integer

, to zostanie użyta jedna wersja, jeśli 

Double

 – druga. Edytor wspiera programistę wyświetla-

jąc informację o wywoływanej procedurze w specyficzny sposób. Ilustruje to kolejne ćwiczenie.

Ćwiczenie 73
Wykorzystać procedurę PobierzLiczbę do pobrania wartości rzeczywistej.

1. Dodaj na końcu procedury Main pusty wiersz i ustaw w nim kursor tekstowy.

2. Zadeklaruj zmienną dblLiczba typu Double o wartości początkowej 7.

3. Zaprogramuj użycie przeciążonej wersji procedury PobierzLiczbę do pobrania wartości 

dla zmiennej dblLiczba, w tym celu:

wpisz nazwę procedury – PobierzLiczbę – i dopisz lewy nawias okrągły,

zauważ, że edytor nieco inaczej przedstawia informację o procedurze i jej parame-

trach:

Rysunek 25: Dodatkowa informacja o istnieniu przeciążonych wersji metody PobierzLiczbę.

background image

użyj klawisza ze strzałką () do wyświetlenia informacji o drugiej wersji procedury:

wpisz nazwę zmiennej dblLiczba i zamknij nawias okrągły.

4. Zaprogramuj wyświetlenie wartości zmiennej dblLiczba.

5. Sprawdź działanie aplikacji.

Kończenie pracy procedury

Działanie procedury kończy się wraz z wykonaniem jej ostatniej instrukcji. Może się jednak oka-

zać, że po wykonaniu pewnej instrukcji procedury dalsza jej praca jest niepotrzebna, albo wręcz 

szkodliwa. W takim przypadku do zakończenia procedury można użyć jednej z dwóch instrukcji:

Exit Sub 'Wyjdź z procedury
Return 'Powrót

Liczba wystąpień każdej z tych instrukcji nie jest ograniczona i można je stosować w tej samej 

procedurze.

Przykład. Procedura pobiera współczynniki a, b, c równania kwadratowego:

ax

2

bxc=0

Jeśli użytkownik wprowadzi dla a wartość 0, to pobieranie dalszych danych nie ma sensu.

Sub PobierzABC(ByRef a As Double, ByRef b As Double, ByRef c As Double)

Console.Write("Współczynnik a równania wynosi: ")
a = CDbl(Console.ReadLine())
If a = 0 Then Exit Sub
'Dalsze instrukcje

End Sub

Wydaje się, że użycie instrukcji  

Exit Sub

, ze względu na słowo kluczowe  

Sub

  jest bardziej 

wskazane – poprawia czytelność kodu. Ponadto słowo 

Return

 ma szczególne znaczenie dla drugie-

go typu metod, to jest funkcji.

VI.2.

Funkcje – metody typu Function

W wielu programach, albo w wielu miejscach jednego programu, może pojawić się ciąg instruk-

cji, których zadaniem jest obliczenie pewnej wartości. Podobnie, jak dla podprogramów, istnieje moż-

liwość nadania takiemu ciągowi nazwy. Ponieważ efektem wykonania nazwanego zestawu instrukcji 

jest raczej określony wynik (wartość funkcji), a nie określony ciąg działań (jak w przypadku procedur), 

to procedura taka może być traktowana jako wartość, którą zwraca – może wystąpić po prawej stro-

Rysunek 26: Informacja o sygnaturze drugiej wersji metody PobierzLiczbę.

background image

nie operatora przypisania. Każda funkcja posiada listę parametrów podlegających takim samym za -

sadom jak w procedurach typu 

Sub

, ponadto rozważania dotyczące sygnatur i przeciążania przeno-

szą się z procedur na funkcje bez zmian. Podstawowa postać deklaracji funkcji jest następująca:

Function NazwaFunkcji(Parametry) As TypWynikuFunkcji

'Instrukcje funkcji, w tym przynajmniej jedna:
'Return Wyrażenie
'lub
'NazwaFunkcji = Wyrażenie

End Function

Słowa kluczowe  

Function

  …  

End Function

  ograniczają blok instrukcji funkcji. Instrukcja 

Return

 Wyrażenie służy do przekazania wyniku funkcji procedurze wywołującej. Do tego celu moż-

na użyć również przypisania:

NazwaFunkcji = Wyrażenie

Różnica pomiędzy tymi sposobami jest taka, że pierwszy z nich (Return) kończy działanie bloku 

funkcji. W drugim przypadku do zakończenia funkcji można użyć instrukcji 

Exit

 

Function

. Kolejne 

ćwiczenie polegać będzie na zdefiniowaniu funkcji, która oblicza wartość wyrazu ciągu:

a

n

=

2n−1

n1

na podstawie wartości n przekazanej jako parametr. Należy zauważyć, że ze względu na uła-

mek występujący we wzorze, wartość wyrazu ciągu, w ogólności, będzie liczbą rzeczywistą.

Ćwiczenie 74
Zdefiniować   funkcję   WyrazCiągu,   która   na   podstawie   parametru   n   (numeru   wyrazu) 
oblicza jego wartość.

1. Dodaj pusty wiersz w module Moje_060 i umieść w nim kursor tekstowy.

2. Zdefiniuj funkcję spełniającą postulaty postawione w zadaniu, w tym celu:

wpisz słowo kluczowe Function, a po spacji nazwę funkcji – WyrazCiągu,

otwórz nawias okrągły i zdefiniuj parametr n typu Integer przekazywany przez 

wartość i zamknij nawias okrągły,

określ typ wyniku funkcji na Double i naciśnij Enter,

zauważ, że edytor zamyka deklarację funkcji słowami kluczowymi End Func-

tion i umieszcza kursor tekstowy wewnątrz bloku instrukcji.

Przykład.

Function WyrazCiągu(ByVal n As Integer) As Double

End Function

background image

3. Zaprogramuj   obliczenie   wartości   wyrazu   ciągu   i   przekazanie   jej   jako   wyniku   funkcji, 

w tym celu:

wpisz słowo Return i dopisz spację,

wpisz wyrażenie obliczające wartość wyrazu ciągu na podstawie wzoru podanego 

przed ćwiczeniem.

Przykład.

Return (2 * n – 1) / (n + 1)

4. Skompiluj projekt.

Deklaracja funkcji może mieć następującą postać:

Procedurę typu 

Function

 można wywołać tak, jak procedurę typu 

Sub

, jednak w takim przy-

padku   następuje   utrata   wyniku   funkcji.  Aby  nie   dopuścić   do   takiej   sytuacji   wywołuje   się   funkcje 

po prawej stronie znaku przypisania, lub w jakimkolwiek innym miejscu kodu, w którym można umie-

ścić wartość określonego typu.

Przykłady. Zakłada się, że zmienna dblWyraz jest typu 

Double

.

dblWyraz = WyrazCiągu(7)
Console.WriteLine("Wyraz ciągu o numerze {0} ma wartość {1}", 11, WyrazCiągu(11))

Kolejne ćwiczenie wykorzystuje funkcję WyrazCiągu do wypełnienia wartościami tablicy.

Ćwiczenie 75
Utwórz nowy Projekt_075 aplikacji konsolowej i dodaj do niego moduł Moje_060. Użyj 
funkcji   WyrazCiągu   do   wypełnienia,   wartościami   pierwszych   pięciu   wyrazów   ciągu, 
tablicy o elementach typu Double. Zaprogramuj wyświetlenie numerów wyrazów i ich 
wartości w postaci tabelki w konsoli.

Efekt działania aplikacji może być podobny do przedstawionego poniżej. Wartości wyrazów cią-

gu formatowano funkcją Format, a wyrównanie uzyskano przy użyciu funkcji RSet.

Rysunek 28: Projekt_075 w działaniu.

Rysunek 27: Kod źródłowy funkcji WyrazCiągu.

background image

Funkcje można wywoływać w innych funkcjach, a także w procedurach. Również procedury 

można wywoływać w funkcjach lub innych procedurach. Podział funkcji (procedur) na mniejsze funk-

cje (procedury), w oparciu o logiczne przesłanki, połączony z używaniem znaczących nazw dla funk-

cji i procedur powoduje, że kod źródłowy jest czytelny i zwarty – łatwiej odnaleźć w nim potencjalne 

błędy.

Funkcję (procedurę) można wywołać także w niej samej – wywołując zjawisko zwane rekuren-

cją.

VI.3.

Rekurencja

Sztandarowym przykładem rekurencji jest matematyczna definicja funkcji silnia:

n !=

{

gdy n=0

n⋅n−1 ! gdy n0

Łatwo zauważyć, że funkcja silnia zdefiniowana jest przez samą siebie, to znaczy, aby obliczyć 

n! trzeba najpierw obliczyć (n-1)!. Żeby móc obliczyć (n-1)! trzeba wcześniej obliczyć (n-2)! itd. Mo-

głoby się wydawać, że taka deklaracja jest nieskuteczna – „tak można w nieskończoność”. Jednak 

oblicza się wartość funkcji silnia, gdyż występuje pewien przypadek elementarny, który kończy obli-

czenia. Koniec obliczeń następuje wtedy, gdy kolejnym argumentem funkcji silnia jest zero. Wtedy 

funkcja zwraca wartość 1. Wartość ta mnożona jest przez poprzedni argument, a iloczyn przez po-

przedni itd., aż do osiągnięcia pierwszego wywołania, które zwraca obliczoną wartość.

Prześledźmy to na przykładzie 3!

A) 3

0, dlatego musimy obliczyć (3-1)!, czyli 2!, a wynik pomnożyć przez 3,

B) 2

0, dlatego musimy obliczyć (2-1)!, czyli 1!, a wynik pomnożyć przez 2,

C) 1

0, dlatego musimy obliczyć (1-1)!, czyli 0!, a wynik pomnożyć przez 1,

D) 0=0, z definicji 0! wynosi 1, wartość 1 jako wynik przekazujemy do wiersza C),

E) 1 pomnożone przez 1 daje 1, wartość tą, jako wynik przekazujemy do wiersza B),

F) 1 pomnożone przez 2 daje 2, wartość tą, jako wynik przekazujemy do wiersza A),

G) 2 pomnożone przez 3 daje 6, wartość tą, jako wynik funkcji przekazujemy do miejsca wywo-

łania.

3!=6.

W kolejnym ćwiczeniu nastąpi próba zdefiniowania rekurencyjnego funkcji silnia. Przyjrzyjmy się 

jeszcze raz definicji:

n !=

{

gdy n=0

n⋅n−1 ! gdy n0

background image

Można ją odczytać następująco: Jeśli argument (parametr) funkcji jest równy zero, to funkcja 

zwraca wartość 1, jeśli jest większy niż zero funkcja zwraca wartość iloczynu argumentu i wartości 

funkcji silnia dla argumentu pomniejszonego o jedność. Ponieważ występują dwa przypadki n=0 i  

n>0, to wydaje się, że blok funkcji może zawierać konstrukcję warunkową  

If

 … 

Then

  … 

Else

 … 

End If

. Warunek powinien być wyrażeniem sprawdzającym czy argument jest równy zeru. Naszki-

cujmy szkielet funkcji:

Function Silnia(ByVal n As Integer) As Integer

If n = 0 Then

Else

End If

End Function

Zdanie: Jeśli argument (parametr) funkcji jest równy zero, to funkcja zwraca wartość 1, tłuma-

czy się na język programowania bardzo prosto:

Return 1

Również drugie zdanie: Jeśli argument (parametr) funkcji jest większy niż zero, to funkcja zwra-

ca wartość iloczynu argumentu i wartości funkcji dla argumentu pomniejszonego o jedność, można  

łatwo przetłumaczyć:

Return n * Silnia(n – 1)

W ostateczności funkcja przyjmie postać:

Function Silnia(ByVal n As Integer) As Integer

If n = 0 Then

Return 1

Else

Return n * Silnia(n – 1)

End If

End Function

Ćwiczenie 76
Utworzyć nowy projekt aplikacji konsolowej i zdefiniować w jego części deklaracyjnej 
funkcję   Silnia.   Zaprogramować   obliczenie   i   wyświetlenie   pierwszych   sześciu   wartości 
funkcji.

Rysunek 29: Projekt_076 w działaniu.

background image

Funkcja Silnia, której typ zdefiniowano na 

Integer

 nie pozwala obliczać wartości n! dla n więk-

szych niż 12. Można to poprawić zmieniając typ wyniku na Int64 (n <= 20). Jeśli nie zależy nam na 

dokładnej wartości całkowitej, to można zwiększyć możliwości funkcji stosując typy rzeczywiste: 

Sin-

gle

 – n <= 34, 

Double

 – n <= 170. Przy tym, w przypadku typów rzeczywistych, przekroczenie poda-

nego zakresu powoduje, że wynik zwracany przez funkcję to „+nieskończoność”.

W matematyce występuje wiele deklaracji, które można zaimplementować rekurencyjnie. Kolej-

nym przykładem jest funkcja obliczająca wartość tzw. Symbolu Newtona. Z czworga dzieci można 

wybrać sześć różnych par:

1 2

1 3

1 4

2 3

2 4

3 4

Liczbę możliwych do ustawienia, różnych układów składających się z k elementów, przy wybo-

rze ze zbioru n elementów określa właśnie funkcja Symbol Newtona (

n
k

– czyta się „n nad k”) 

określona wzorem:

n
k

=

n !

k !⋅ n!

Pamiętając, że 0! jest równe 1, można zauważyć, że jeśli k jest równe zeru, to wzór sprowadza 

się do

n !
n !

, a to niezależnie od wartości n wyniesie 1. Podobnie, jeśli k jest równe n, to wzór ponow-

nie sprowadza się do

n !
n !

, czyli do jedności. Korzystając z definicji silni można przepisać wzór ogól-

ny w następujący sposób:

n
k

=

n !

k !⋅n!

=

n⋅n−1!

k⋅ −1!⋅n !

=

n
k

n−1!

−1!⋅n!

Napiszmy poniżej wzór dla wartości n-1 i k-1:

n−1
−1

=

n−1!

−1!⋅ n−1−−1!

=

n−1!

−1 !⋅ n−1−1!

=

n−1!

−1!⋅n!

Porównując wzory (patrz: strzałka) można zauważyć, że:

n
k

=

n
k

n−1
−1

background image

Zbierając  wszystkie wzory w  jedną deklarację otrzymujemy „ładną”, rekurencyjną deklarację 

funkcji.

n
k

=

{

gdy k =0

gdy k =n

n

k

n−1
−1

gdy k ≠0 i kn

W powyższym wzorze „zgubiła się” funkcja silnia. To pierwszy sukces rekurencji w tym przypad-

ku, gdyż korzystanie z dokładnych wartości funkcji silnia ograniczało jej argument do liczby 20. Drugi 

sukces polega na tym, że obliczanie wartości funkcji Symbol Newtona na podstawie pierwotnej defi -

nicji wymagało wykonania n+k+(n-k)+2=2n+2 mnożeń (w tym jedno dzielenie). Definicja rekurencyjna 

pozwala ograniczyć liczbę mnożeń (i dzieleń) do 2k. Przy k bliskich n zysk nie jest wielki, jednak 

można zastosować pewną tożsamość, a mianowicie:

n
k

n

nk

która powoduje, że n-k jest bliższe zeru. Tożsamość ta pozwala zredukować dolny argument do war-

tości mniejszych niż n/2, zatem zmniejsza liczbę mnożeń i dzieleń do wartości mniejszej niż n. Wyni -

ka stąd, że korzystanie z rekurencji skróci czas obliczeń o więcej niż połowę, ponadto umożliwi obli -

czenia dla argumentów większych niż 20. Biorąc powyższe pod uwagę napiszemy ostateczny wzór:

n
k

=

{

gdy k=0

gdy k =n

n

k

n−1
−1

gdy k ≠0 i k n i 2k ≤n

n

nk

gdy k ≠0 i k n i 2kn

Pierwsze dwa warunki można sprawdzać łącznie, gdyż funkcja zwraca tą samą wartość. Pe-

wien problem stanowi ułamek w trzecim wierszu definicji. Polega on na tym, że Symbol Newtona  

z zasady jest liczbą całkowitą, a dzielenie w ogólności daje liczbę rzeczywistą. Będzie ona oczywi-

ście pozbawiona części ułamkowej, jednak kompilator będzie proponował zastosowanie funkcji kon-

wersji. Jednak pamiętając o tym, że wynik funkcji jest całkowity, można problem obejść stosując ope -

rator dzielenia całkowitego „\”, który daje wynik całkowity. Przy tym, ponieważ obliczenie wartości 

funkcji musi nastąpić, to należy wywołanie funkcji ustawić jako pierwsze w wyrażeniu:

Return SymbolNewtona(n - 1, k - 1) * n \ k

W kolejnym ćwiczeniu należy zdefiniować funkcję w oparciu wzór:

n
k

=

{

gdy k=0

gdy k =n

n

k

n−1
−1

gdy k ≠0 i k n i 2k ≤n

n

nk

gdy k ≠0 i k n i 2kn

background image

Ćwiczenie 77
Utworzyć nowy projekt aplikacji konsolowej i zdefiniować w części deklaracyjnej modułu 
rekurencyjną   funkcję   SymbolNewtona   typu   Integer   o   dwóch   parametrach   n,   k   typu 
Integer przekazywanych przez wartość realizującą wzór podany przed treścią ćwiczenia. 
Zaprogramować wyświetlenie wartości funkcji dla n zmieniających się od 0 do 14 i k 
zmieniających się od 0 do n.

Efekt działania aplikacji (zastosowano funkcję RSet) może być taki, jak na rysunku (jest to, tzw. 

trójkąt Pascal’a):

Nie   zawsze  rekurencja  pozwala   osiągnąć  zmniejszenie  czasu  pracy  funkcji,   a  w  niektórych 

przypadkach   powoduje   nawet   efekt   odwrotny.   Przykładem   jest   ciąg   Fibbonacciego   zdefiniowany 

w następujący sposób:

F

n

=

{

gdy n=0
gdy n=1

F

n−2

F

n−1

gdy n1

Sprawdzenie działania rekurencyjnej deklaracji tego ciągu pozostawia się ćwiczącym do samo-

dzielnego wykonania. Ciekawym doświadczeniem będzie próba wyświetlenia pierwszych 50 wyrazów 

ciągu (ustalić typ funkcji na Int64). Z pewnością (od wartości n większych niż 30) da się zaobserwo -

wać znaczny wzrost czasu obliczania kolejnych wartości.

Również procedury można programować rekurencyjnie. Rozważmy następujący przykład. Pro-

gram losuje liczbę z przedziału <1, 100> i dalsze jego działanie polega na kilkukrotnym sprawdzeniu  

czy liczba podana przez użytkownika jest równa wylosowanej. Liczba prób może być ustalana jako 

poziom trudności zadania i powinna być parametrem procedury. Również liczba wylosowana przez 

program powinna być przekazana do procedury jako parametr.

Jakie działania powinna wykonywać potencjalna procedura realizująca sprawdzenie. Z pewno-

ścią powinna pobrać liczbę typowaną przez użytkownika. Mogą wystąpić trzy przypadki:

Liczba podana przez użytkownika jest równa liczbie wylosowanej przez program. Procedura 

powinna powiadomić o tym użytkownika i zakończyć pracę (Exit Sub).

Rysunek 30: Projekt_077 w działaniu.

background image

Liczba podana przez użytkownika jest za mała. Procedura powinna powiadomić o tym użyt-

kownika.

Liczba podana przez użytkownika jest za duża. Procedura powinna powiadomić o tym użyt-

kownika.

Dwa ostatnie przypadki nie kończą procedury, ale kończą próbę podjętą przez użytkownika, na-

leży to uwzględnić zmniejszając o jeden liczbę pozostałych prób. Dalszym działaniem powinno być 

sprawdzenie, czy użytkownikowi pozostały jeszcze próby. Mogą wystąpić dwa przypadki:

Liczba pozostałych prób wynosi zero. Procedura powinna powiadomić o tym użytkownika i za-

kończyć pracę.

Pozostały jeszcze próby. Należy wykonać kolejne sprawdzenie. Procedura powinna wywołać 

samą siebie, przekazując wylosowaną liczbę oraz liczbę pozostałych prób.

Kolejne ćwiczenie polegać będzie na stworzeniu rekurencyjnej procedury sprawdzającej i wyko -

rzystanie jej w programie.

Ćwiczenie 78
Utworzyć nowy projekt aplikacji konsolowej. Zdefiniować w części deklaracyjnej modułu 
procedurę Sprawdź, o dwóch parametrach typu  Integer przekazywanych przez wartość 
(wylosowana liczba, liczba prób), realizującą opisany wcześniej schemat działania. W 
programie   zainicjować   generator   liczb   losowych,   zaprogramować   losowanie   liczby   z 
zakresu <1, 100> i uruchomić procedurę z parametrami: wartość wylosowanej liczby i 7.

Efekt działania aplikacji może być taki, jak na rysunku:

Rysunek 31: Projekt_078 w działaniu - sukces użytkownika.

background image

lub taki jak na kolejnym rysunku:

Rysunek 32: Projekt_078 w działaniu - porażka użytkownika.


Document Outline