PO wyk04 v1

background image

Funkcje

Kiedy ktoś mówi o C++ to ma na myśli przede wszystkim obiekty. Jednak
obiekty bazują, na funkcjach, które wykonują niezbędne do działania obiektu
operacje.

Każdy program w C++ posiada przynajmniej jedną funkcję, main () . Kiedy
uruchamia się program to funkcja main () jest automatycznie uruchamiana.
Może ona wywoływać inne funkcje, które z kolei mogą wywołać jeszcze inne.

Funkcja jest podprogramem, który może modyfikować dane i zwracać

wartość.

Każda funkcja ma swoją nazwę, a wywołanie funkcji polega na wpisaniu jej
nazwy w programie. W momencie napotkania wywołania funkcji program
przechodzi do wykonania kodu funkcji. Kiedy funkcja się kończy, to program
wraca do miejsca jej wywołania (do następnej instrukcji).

Ilustracja tego procesu

return;

Funkcja
4

Main()
{
Instrukcja;
Funkcja1 () ;
Instrukcja ;
Funkcja2 () ;
Instrukcja ;
Funkcja4 () ;
}

return;

Funkcja
1

return;

Funkcja
3

Instrukcja;
Funkcja3();

return;

Funkcja
2

background image

Deklarowanie funkcji (prototypy)

Deklaracja funkcji określa jej nazwę, typ zwracanej wartości i listę
parametrów.

Standardowe funkcje dostarczone razem z kompilatorem posiadają już swoje
prototypy. Wystarczy dołączyć za pomocą #include odpowiedni plik
nagłówkowy (.H)
Prototyp funkcji to typ wartości zwracanej przez tę funkcję, nazwa i lista
parametrów. Prototyp kończy się znakiem średnika.
Lista

parametrów

to

wyszczególnienie

wszystkich

parametrów

przekazywanych do funkcji oddzielonych przecinkami.

unsigned short int PoliczPole ( int nDlugosc, int nSzerokosc);

typ zwracanej
wartości

nazw
a

parame
try

średn
ik

nazwa
parametru

typ
parametru

Przykład:Elementy prototypu

Żadna funkcja nie może być wywołana przez inną bez uprzedniego
zadeklarowania.

Deklaracja funkcji nazywana jest prototypem.

Definicja obejmuje treść funkcji.

background image

Prototyp, pod względem typu wartości zwracanej, nazwy i typów parametrów,
musi zgadzać się z definicją funkcji.

Jeśli wystąpią różnice, to kompilator, przy próbie kompilacji,
zasygnalizuje błąd.

Przykład: long

Pole

( int , int );

Ten prototyp deklaruje funkcję

Pole ()

zwracającą wartość typu long i

posiadającą dwa parametry typu int. Mimo że taka deklaracja jest całkowicie
prawidłowa, to dla poprawienia przejrzystości zalecane jest podawanie w
prototypie również nazw parametrów. Deklaracja z nazwami parametrów
będzie wyglądać następująco:

long

Pole

( int nDlugosc, int nSzerokosc

) ;

Zauważmy, że wszystkie funkcje mają określony typ zwracanej wartości.

Prototyp funkcji mówi kompilatorowi o nazwie funkcji, wartości zwracanej i
parametrach.

typ_zwracany

nazwa_funkcji

( [

typ

[

nazwa_parametru

]]….)

;

składnia:

background image

Porównajcie prototyp z definicją
funkcji.
Zwróćcie uwagę, że typ wartości
zwracanej,

nazwa

i

typy

parametrów są identyczne.
Gdyby były jakiekolwiek różnice to
kompilator wygenerowałby błąd.

Praktycznie jedyna wymagana
różnica polega na tym, ze
prototyp kończy się średnikiem
i nie zawiera treści funkcji.

Zwróćcie także uwagę na to, że
nazwy parametrów w prototypie to
nDlugosc i nSzerokosc, a nazwa
parametrów w definicji to d i s. Jak
widać,

nazwy

parametrów

w

prototypie nie są używane, służą
one jedynie jako informacja dla
programisty.

background image

Definiowanie funkcji

Definicja funkcji składa się z nagłówka i treści funkcji. Nagłówek wygląda tak,
jak prototyp, jednak musi posiadać nazwy parametrów i nie może być
zakończony średnikiem.
Treści funkcji to zbiór instrukcji ograniczony klamrami.

unsigned short int ZnajdzPole ( int nDługosc, int
nSzerokosc )

{

// instrukcje

return ( nDlugosc*nSzerokosc ) ;

}

Klamra
zamykająca

Klamra
otwierająca

słowo
kluczowe

zwracana
wartość

typ zwracanej
wartości

nazw
a

parametry

background image

Definicja mówi kompilatorowi co dana funkcja robi i jak działa.

typ_zwracany

nazwa_funkcji

( [

typ

[

nazwa_parametru

]]…. )

{

instrukcje;

}

Składnia:

Jeżeli funkcja zwraca jakąś wartość, to przed wyjściem z funkcji należy użyć
instrukcji return. Instrukcja ta może zostać użyta w każdym miejscu treści
funkcji.
Dla każdej funkcji określany jest typ wartości zwracanej. Jeśli nie podamy
typu, to automatycznie zostanie przypisany typ całkowity – int. Jeśli funkcja
nie zwraca żadnej wartości, to typem zwracanym będzie void.
Przykłady prototypów funkcji:

long Pole (long lDlugosc, long lSzerokosc);

Zwraca long,

ma dwa parametry

void WypiszTekst(int nNumerTekstu);

Zwraca void,

ma jeden parametr

int PobierzOpcje ();

Zwraca int,

brak parametrów

SpecFunk () ;

Zwraca int,

brak parametrów

background image

Przykłady poprawnych definicji funkcji:

void WypiszTekst( int nNumerTekstu )
{
if ( nNumerTekstu == 0)
{
cout << "Czesc.\n";
}
if ( nNumerTekstu == 1)
{
cout << "Do widzenia. \n";
}
if ( nNumerTekstu > 1)
{
cout << "Jestem troche zaklopotany. \n";
}
}

long Pole ( long d, long s )
{
return d*s;
}

background image

Zmienne lokalne

Zmienne można nie tylko przekazywać do funkcji. Można je również
deklarować wewnątrz funkcji. Wykorzystuje się w tym celu tzw. zmienne
lokalne. Zmienne te są widoczne tylko wewnątrz funkcji, w której są
zadeklarowane. Kiedy funkcja się kończy, zmienne przestają być dostępne.
Zmienne lokalne definiuje się tak samo jak wszystkie inne. Parametry
przekazywane do funkcji również są traktowane jako zmienne lokalne i można
je wykorzystywać tak, jakby były wewnątrz tej funkcji zadeklarowane.

background image

Wartość

przekazywana

jako

parametr, fTempFer, również
jest tylko lokalną kopią zmiennej
przekazywaną z funkcji main ().

Deklarowana

jest

zmienna

lokalna fTempCel. Ta zmienna
istnieje tylko wewnątrz funkcji
Konwertuj ().

Zwróćmy uwagę, że nie jest to ta
sama zmienna co fTempCel.

background image

Każda zmienna ma swój zasięg,
który mówi jak długo zmienna
jest dostępna i gdzie można z niej
korzystać.

Zmienne

zadeklarowane w osobnym bloku
widoczne są tylko wewnątrz niego
i giną wraz z końcem bloku.

background image

Zmienne globalne

Zmienne zadeklarowane poza wszystkimi funkcjami mają globalny zasięg i
widoczne są wewnątrz wszystkich funkcji, łącznie z main (). Gmatwają one
bardzo program.

W profesjonalnie napisanych programach, są bardzo rzadko
spotkane.

Argumenty funkcji

Argumenty funkcji nie muszą być tego samego typu. Nie ma żadnych
przeciwwskazań, żeby argumentami funkcji były np.: jedna zmienna typu int,
dwie typu float i jedna typu char.
Każde poprawne wyrażenie może być argumentem funkcji. Mam tu na myśli
również stałe, wyrażenia matematyczne i logiczne oraz inne funkcje
zwracające wartość.

background image

Funkcje jako parametry innych funkcji

Mimo że można używać funkcji zwracających wartości jako parametry do
innych funkcji, to prowadzi ono do zbędnego komplikowania kodu.

Przykład:

Załóżmy, że mamy funkcje RazyDwa (), RazyTrzy (), Kwadrat () i Szescian () ,
z których każda zwraca wartość. Można napisać:

lOdp = ( RazyDwa( RazyTrzy( Kwadrat( Szescian( lWartosc )))));

Ta instrukcja pobiera zmienną lWartosc, przekazuje ją jako argument do
funkcji Szescian (), zwracana wartość przekazywana jest z kolei od funkcji
Kwadrat(), której wynik przekazywany jest do funkcji RazyTrzy () w
rezultacie której operuje funkcja RazyDwa () . Wynik tego podwajania,
potrajania i potęgowania podstawiany jest do zmiennej lOdp.

Trudno na pierwszy rzut okna powiedzieć co ta instrukcja robi (czy wartość
była podnoszona do sześcianu zanim była podwojona czy nie?).
Również w momencie uzyskania wyniku niezgodnego z oczekiwaniami trudno
będzie znaleźć miejsce popełnienia błędu.

background image

Alternatywnym rozwiązaniem jest przypisanie każdego kroku do osobnych
zmiennych:

unsigned long lWartosc = 2 ;

unsigned long lPotega3 = Szescian( lWartosc );// lPotenga3 =
8

unsigned long lPotega2 = Kwadrat( lPotega3 ); // lPotenga2 =
64

unsigned long lPrzez3 = RazyTrzy( lPotega2 );// lPrzez3 =
192

unsigned long lPrzez2 = RazyDwa( lPrzez3 ); // lPrzez2 =
384

Teraz każda pośrednia wartość może zostać przeanalizowana. Jasna jest
również kolejność wykonywania operacji.

background image

Parametry są zmiennymi lokalnymi

Argumenty przekazywane do funkcji są w niej lokalne. Zmiana ich wartości
jest również lokalna i nie jest widoczna w funkcji wywołującej. Nazywamy to
przekazywaniem przez wartość, co oznacza, że w funkcji tworzona jest
lokalna kopia każdego argumentu przekazywanego do tej funkcji. Takie kopie
są traktowane tak, jak lokalne zmienne.

Ten program, w funkcji main() ,
inicjalizuje

dwie

zmienne

i

przekazuje je do funkcji Zamien(),
która teoretycznie je zamienia.

Jak

widać,

zmienne

zostały

przekazane do funkcji tylko przez
wartość. Zostały stworzone ich
lokalne kopie, i na tych kopiach
były

wykonywane

wszelkie

operacje. Były to lokalne zmienne
funkcji Zamien(). Zamiana została
dokonana tylko na kopiach i nie
miała żadnego wpływu na wartości
zmiennych w funkcji main().

Jednak po przetestowaniu wyniku w
funkcji main() okazuje się, że nic
się nie zmieniło!

background image

Zwracanie wartości

Funkcje mogą zwracać albo jakąś wartość albo void. void to informacja dla
kompilatora, że funkcja nie będzie zwracać wartości. Żeby zwrócić wartość z
funkcji, należy użyć słowa kluczowego return, a następnie podać wartość,
która ma zostać zwrócona. Równie dobrze może być to wyrażenie zwracające
wartość.

Przykład:

return 5 ;

return ( x > 5 ) ;

return (MojaFunkcja() );

Po napotkaniu słowa kluczowego return wartość wymieniona po return jest
zwracana jako wartość funkcji, a program wraca do funkcji wywołującej.
Krótko mówiąc, instrukcja return, kończy wykonywanie danej funkcji.

Wartość zwrócona będzie równa zero
jeżeli x będzie nie większe niż 5, w
przeciwnym wypadku będzie równa
1. To co jest zwracane to wartość
wyrażenia, 0 (fałsz) lub 1 (prawda), a
nie wartość zmiennej x.

W jednej funkcji można wielokrotnie wykorzystywać instrukcję return.
Wykonanie instrukcji return powoduje zakończenie funkcji.

background image

Funkcja Denominator() sprawdza, czy podana
liczba

nie

jest

większa

od

stałej

GRANICAGORNA

. Jeżeli nie, to funkcja zwraca

jej

wartość

pomnożoną

przez

stałą

DEWALUACJA

. Jeżeli jednak liczba jest większa

od 1000, to funkcja zwraca stałą

ERROR

jako

wartość błędną.

Instrukcja nigdy nie zostanie wykonana,
ponieważ niezależnie od wartości parametru
(większy od 1000 czy nie) funkcja zawsze wróci
albo albo .

Dobry kompilator zasygnalizuje, że ta

instrukcja

nigdy nie zostanie osiągnięta w trakcie
wykonywania programu. Niektóre kompilatory
zwrócą nawet komunikat błędu. Można tę linię
programu wykomentować.

background image

Parametry domyślne

Do każdego zadeklarowanego w prototypie i definicji funkcji parametru,
funkcja wywołująca musi pobrać wartość zgodną z zadeklarowanym typem.
Przykład:
Jeżeli funkcję zadeklarowaną w następujący sposób:

long Funkcja ( int ) ;

to trzeba przekazywać do niej wartość całkowitą. Jeżeli definicja jest
niezgodna z prototypem, lub jeżeli wartość przekazywana nie będzie
całkowita to kompilator zasygnalizuje błąd.

Od tej reguły jest jeden

wyjątek

. Jeżeli w prototypie zadeklarujemy domyślną wartość dla parametru,

to gdy nie określimy danego argumentu, zostanie mu przypisana
automatycznie wartość domyślna. Odpowiednia deklaracja powinna wyglądać
następująco:

long Funkcja ( int x = 10 ) ;

Definicja funkcji nie zmienia się. Nagłówek definicji nadal będzie miał postać:

long Funkcja ( int x )

Teraz, gdy wywoła się funkcję bez określenia wartości argumentu to
kompilator automatycznie nada mu wartość 10. Nazwa parametru z
wartością domyślną nie musi być taka sama jak w nagłówku definicji, wartość
domyślna jest przypisywana na podstawie pozycji w liście parametrów, a nie
według nazwy.

background image

Wartości domyślne można nadawać kilku parametrom funkcji. Jest tu jednak
pewne ograniczenie, jeżeli parametr nie ma określonej wartości domyślnej, to
żaden z poprzedzających go parametrów, również nie może posiadać
wartości domyślnej.

Jeżeli prototyp funkcji wygląda w następujący sposób:

long Funkcja ( int nParam1, int nParam2, int nParam3 ) ;

Przykład:

to parametrowi nParam2 można przypisać wartość domyślną wtedy i tylko
wtedy gdy przypisze się również wartość domyślną do nParam3. Podobnie,
gdy chce się przypisać wartość domyślną do nParam1 to trzeba uwzględnić
parametry nParam2 i nParam3 i również nadać im wartości domyślne.

background image

Deklaracja funkcji ObjProstopadłościanu()
jako funkcję mającą trzy parametry. Dla dwóch
ostatnich są zadane wartości domyślne.

Jeżeli nie poda się nSzerokosci i nWysokosci
to program wykorzysta podane wartości: 10 i
3.

Zdeklarowane i zainicjalizowane zmienne
przekazywane są do funkcji
ObjProstopadlościanu().

Wywołujemy

funkcję

ObjProstopadloscianu(), lecz tym razem bez
parametru nWysokosc. Program wykorzystuje
wartość domyślną (3).

Trzecie

wywołanie

funkcji

ObjProstopadloscianu() Tym razem podany
jest

tylko

jeden

parametr

nDlugosc.

Pozostałym

parametrom

nadawane

są,

wartości domyślne.

background image

Przeciążanie funkcji

C++ pozwala na stworzenie więcej niż jednej funkcji o tej samej nazwie.
Możliwość ta określana jest jako przeciążanie funkcji. Funkcje takie
muszą różnić się listą parametrów, typami parametrów lub ich liczbą.

Przykład:

int FunkcjaNew ( int, int ) ;

int FunkcjaNew ( long, long ) ;

int FunkcjaNew ( long ) ;

FunkcjaNew () jest przeciążona z użyciem trzech rożnych list parametrów.
Dwie pierwsze różnią się typem parametrów, trzecia ma inną ich liczbę.

W przypadku przeciążanych funkcji, typ wartości zwracanej może być
inny lub taki sam. Nie można przeciążać funkcji opierając się jedynie na
typie wartości zwracanej. Trzeba wykorzystać różne listy parametrów.

Przeciążanie funkcji określane jest również mianem

polimorfizmu

funkcji

.

background image

Polimorfizm pozwala na przeciążanie funkcji za pomocą różnych ich treści.
Zmieniając liczbę lub typ parametrów, można nadać funkcjom te same
nazwy. W momencie wywołania, parametry będą decydować, która z funkcji
zostanie wykonana.

Załóżmy, że potrzebujemy funkcję podwajającą dowolną podaną wartość.
Chcielibyśmy mieć możliwość podania zmiennej typu int, long, float i
double.

Przykład:

Bez

przeciążania

funkcji

musiałoby się napisać cztery
funkcje o różnych nazwach:

int PodInt ( int ) ;

long PodLong ( long ) ;

float PodFloat ( float ) ;

doube PodDouble ( double ) ;

Natomiast

z

użyciem

przeciążenia

można

użyć

następującej deklaracji:

int Pod ( int ) ;

long Pod ( long ) ;

float Pod ( float ) ;

double Pod ( double ) ;

background image

Funkcje wewnętrzne (inline)

Gdy definiuje się funkcję, kompilator tworzy w pamięci jeden zestaw
instrukcji. W momencie wywołania funkcji, program przechodzi do wykonania
tych instrukcji. Gdy funkcja kończy swoje działanie, program wraca do
następnej instrukcji po wywołaniu.

Jeżeli funkcja jest zadeklarowana ze słowem kluczowym inline, to
kompilator nie tworzy prawdziwej funkcji. Kopiuje natomiast jej kod w
każde miejsce wywołania. Nie jest wykonywany żaden skok. Wygląda to
tak, jakby fizycznie wpisać treść funkcji zamiast ją wywoływać.

gdy funkcję wywołuje się 10 razy

program tyle samo razy "skoczy" do instrukcji danej

funkcji.

w pamięci istnieje tylko jedna kopia funkcji, (a nie

10).

Prędkość
wykonywania
programu
zwiększa się gdy
unikniemy skoków
do funkcji.

background image

Kiedy zatem stosować funkcje inline?

Wtedy gdy ma się małą funkcję (dwie, trzy linie), to można pomyśleć o

zamienieniu jej na wewnętrzną.

Jeżeli wywoła się taką funkcję dziesięciokrotnie to treść funkcji zostanie
skopiowana w każde z dziesięciu miejsc wywołania. Niewielki wzrost
wydajności może okazać się nieopłacalny w stosunku do wzrostu rozmiaru
kodu wynikowego.

Funkcje inline kosztują (pamięć).

background image

Deklaracja

wygląda

jak

zwykły

prototyp, jedyna różnica polega na
użyciu słowa kluczowego inline. Taka
deklaracja funkcji powoduje, że w
tych miejscach:

nLiczba = 2 * nLiczba ;

nLiczba = 2 * nLiczba ;

nLiczba = 2 * nLiczba ;

background image

Przed

wykonaniem

programu,

kompilator

umieszcza instrukcje funkcji
w kodzie. Oszczędza się w
ten

sposób

na

liczbie

skoków wewnątrz kodu, lecz
traci

na

rozmiarze

programu.

background image

Bliższe spojrzenie na działanie funkcji

Wywołując funkcję, program przechodzi do wykonania instrukcji danej funkcji
i przekazuje parametry. Kiedy funkcja się kończy, zwracana jest wartość
(ewentualnie void) i program wraca do miejsca, z którego funkcja została
wywołana.

Jak to zadanie jest zorganizowane?

 Skąd program wie, dokąd ma przejść?

 Gdzie są przechowywane zmienne przekazywane do funkcji?

 Co się dzieje ze zmiennymi deklarowanymi wewnątrz funkcji?

 W jaki sposób jest przekazywana wartość zwracane przez funkcję?

 Skąd program wie, dokąd ma wrócić po wykonaniu funkcji?

Stos

Kiedy program rozpoczyna działanie, to kompilator tworzy stos.

Stos to specjalny obszar pamięci (struktura danych) służąca do
przechowywania danych wymaganych przez wszystkie funkcje w
programie. Określenie stos wynika z działania tej struktury, wartość,
która została położona na stos jako ostatnia zostanie zdjęta jako
pierwsza (LIFO ang. Last-in first-out).

background image

Lepiej jest wyobrażać sobie stos jako odpowiedni ciąg komórek pamięci
ustawiony "do góry nogami". Szczyt jest tam gdzie wskazuje wskaźnik stosu.

100

101

102

103

104

105

106

107

108

109

110

80

50

37

Stos

Zmienna

nMojaKasa

nTwojaKasa

poza stosem

na stosie

102

Wskaźnik
stosu

Każda komórka stosu ma swój
adres. Jeden z tych adresów jest
przechowywany

w

rejestrze

stosu. Wszystko poniżej tego
adresu, nazywanego szczytem
stosu, jest traktowane jako
położone na stosie. Wszystko
powyżej jest poza stosem.

background image

100

101

102

103

104

105

106

107

108

109

110

80

50

37

Stos

Zmienna

nMojaKasa

nTwojaKasa

poza stosem

na stosie

108

Wskaźnik
stosu

Kiedy wartość jest odkładana na stos, to jest umieszczana w komórce
powyżej wskaźnika stosu. Wskaźnik stosu jest przesuwany na tą komórkę.
Kiedy wartość jest zdejmowana, to faktycznie zmieniany jest tylko wskaźnik
stosu.

background image

Stos i funkcje

Kiedy program wywołuje funkcję to tworzy dla niej ramkę stosu. Ramka stosu
to obszar na stosie, przeznaczony dla danej funkcji. Jest to bardzo ogólne i
rożnie wykonywane na rożnych komputerach. Można jednak wyróżnić kilka
podstawowych kroków:

Umieść na stosie adres powrotny. Kiedy funkcja się skończy, to

program wróci do tego adresu.

Zrób na stosie miejsca dla zadeklarowanej wartości zwracanej

przez funkcję.

Umieść na stosie argumenty funkcji.

Przejdź do wykonywania funkcji.

Umieść na stosie zmienne lokalne funkcji według ich definicji.


Document Outline


Wyszukiwarka

Podobne podstrony:
PO wyk07 v1
postfix krok po kroku v1 1
PO wyk06 v1
PO wyk02 v1
Szkolenie Q Motion Controllers po polsku V1 2 3 11 2003
PO wyk01 v1
PO wyk05 v1
PO wyk12 v1
PO wyk08 v1
PO wyk07 v1
postfix krok po kroku v1 1
PO wyk06 v1
postfix krok po kroku v1 0
WR 1273252 v1 Skrypt po breaku 1
Rehabilitacja po endoprotezoplastyce stawu biodrowego
Systemy walutowe po II wojnie światowej
HTZ po 65 roku życia
Zaburzenia wodno elektrolitowe po przedawkowaniu alkoholu

więcej podobnych podstron