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.
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().
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.
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
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.
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
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ę.
Ć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ę.
Ć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
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.
Ć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.
Ć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ę.
◦
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ę.
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
n1
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
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.
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 !=
{
1 gdy n=0
n⋅n−1 ! gdy n0
Ł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 !=
{
1 gdy n=0
n⋅n−1 ! gdy n0
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.
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−k !
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−k !
=
n⋅n−1!
k⋅ k −1!⋅n−k !
=
n
k
⋅
n−1!
k −1!⋅n−k !
Napiszmy poniżej wzór dla wartości n-1 i k-1:
n−1
k −1
=
n−1!
k −1!⋅ n−1−k −1!
=
n−1!
k −1 !⋅ n−1−k 1!
=
n−1!
k −1!⋅n−k !
Porównując wzory (patrz: strzałka) można zauważyć, że:
n
k
=
n
k
⋅
n−1
k −1
Zbierając wszystkie wzory w jedną deklarację otrzymujemy „ładną”, rekurencyjną deklarację
funkcji.
n
k
=
{
1 gdy k =0
1 gdy k =n
n
k
⋅
n−1
k −1
gdy k ≠0 i k≠n
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
n−k
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
=
{
1 gdy k=0
1 gdy k =n
n
k
⋅
n−1
k −1
gdy k ≠0 i k ≠n i 2k ≤n
n
n−k
gdy k ≠0 i k ≠n i 2kn
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
=
{
1 gdy k=0
1 gdy k =n
n
k
⋅
n−1
k −1
gdy k ≠0 i k ≠n i 2k ≤n
n
n−k
gdy k ≠0 i k ≠n i 2kn
Ć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
=
{
1 gdy n=0
1 gdy n=1
F
n−2
F
n−1
gdy n1
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.
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.
lub taki jak na kolejnym rysunku:
Rysunek 32: Projekt_078 w działaniu - porażka użytkownika.