11.2 Deklaracje i definicje funkcji
Dalej: 11.3 Wywołanie funkcji
W górę: 11. Funkcje
Wstecz: 11.1 Wstęp
11.2 Deklaracje i definicje funkcji
Definicja lub deklaracja dostarcza
kompilatorowi informacji o funkcji: jaki jest jej typ zwracany,
jaka jest liczba parametrów, jaki jest typ parametrów
itd. W ten sposób za każdym razem, gdy w dalszej części
tekstu programu pojawia się użycie tej funkcji, kompilator może
sprawdzić
o którą z funkcji o tej samej nazwie chodzi (może być
wiele funkcji o tej samej nazwie);
many functions with the same name);
czy wywołanie funkcji jest prawidłowe; czy na przykład
ilość i typ argumentów odpowiada ilości i typom
parametrów, czy typ wartości zwracanej
przez funkcje jest w danym miejscu programu
akceptowalny, itd.
W razie niezgodności kompilacja zostanie przerwana, co jest lepsze
niż utworzenie bezsensownego kodu wynikowego. Jest to cecha
C++; w tradycyjnym C wymogu wcześniejszego deklarowania funkcji
nie było, co powodowało moc kłopotów przy testowaniu i
uruchamianiu programów.
Dlaczego definicja lub deklaracja, co to w ogóle jest deklaracja
funkcji i do czego się przydaje?
Wyobraźmy sobie następującą sytuację: definiujemy kolejno
dwie funkcje,
fun1 i
fun2. Funkcja
fun1
wywołuje w swej treści funkcję
fun2 i odwrotnie,
funkcja
fun2 wywołuje w swej treści funkcję
fun1:
1. void fun1(int k) {
2. // ...
3. fun2(k)
4. // ...
5. }
6.
7. void fun2(int m) {
8. // ...
9. fun1(m)
10. // ...
11. }
W jakiej kolejności zdefiniować te funkcje? Jeśli zdefiniujemy je
tak jak wyżej, to w linii trzeciej kompilacja zostanie przerwana,
bo nieznana jest w niej jeszcze funkcja
fun2; odwrócenie
kolejności nie pomoże, bo wtedy instrukcja wywołania
fun1
wewnątrz
fun2 spowoduje te same kłopoty.
Na szczęście jest wyjście z tej sytuacji. Kompilatorowi nie jest
potrzebna definicja funkcji, to znaczy nie musi wiedzieć,
co funkcja robi: musi tylko wiedzieć, ile i jakie ma parametry i jaki
jest jej typ zwracany. Do tego wystarczy
prototyp
funkcji
podany w deklaracji.
Deklaracja ma formę nagłówka funkcji, po którym następuje średnik
zamiast ciała (treści) funkcji. Na przykład poprawnymi deklaracjami
funkcji są
string& fun1(char* c1, char* c2, bool b);
void fun2(int k, double d[]);
Klasa* fun3(Klasa* k1, Klasa* k2);
Nie mają one ciała (treści), a więc nie są definicjami.
Ale zawierają informacje o nazwie, typie zwracanym,
typie i liczbie parametrów (czyli właśnie prototyp).
Jest to wszystko, czego potrzebuje kompilator, aby sprawdzić
formalną prawidłowość ich użycia. Tak więc nasz pierwszy przykład
skompiluje się gładko, jeśli przed definicją funkcji
fun1 umieścimy
deklarację funkcji
fun2 (ze średnikiem na końcu):
1. void fun2(int);
2.
3. void fun1(int k) {
4. // ...
5. fun2(k)
6. // ...
7. }
8.
9. void fun2(int m) {
10. // ...
11. fun1(m)
12. // ...
13. }
Zauważmy, że w deklaracji (linia 1) nie podaliśmy nazwy
pierwszego i jedynego parametru formalnego funkcji
fun2
- tylko jego typ (
int). Jest to całkowicie dopuszczalne:
do sprawdzenia poprawności wywołania kompilator potrzebuje informacji
o liczbie i typie parametrów funkcji,
ale ich nazwy nie są do niczego potrzebne i są wobec tego
przez kompilator pomijane. Zatem można ich w ogóle nie pisać
(choć warto, bo umiejętnie dobrane nazwy są znakomitą formą
komentarza). Oczywiście w definicji (linia 9), nazwa zwykle
jest konieczna, ale nie musi być taka sama jaka została podana w
deklaracji.
Na przykład, podane poprzednio trzy deklaracje
moglibyśmy równie dobrze zapisać tak:
string& fun1(char*, char*, bool);
void fun2(int, double[]);
Klasa* fun3(Klasa*, Klasa*);
Funkcja zadeklarowana musi oczywiście być gdzieś również
zdefiniowana (tylko raz). W przeciwnym razie powstanie błąd na
etapie linkowania (łączenia, konsolidacji) programu. Jednak definicja
nie musi nawet wystąpić w tym samym module (pliku). Wystarczy, że
umieścimy ją w jakimś module składającym się na cały program.
W innych modułach, w których funkcja ta jest używana, trzeba tylko
zamieścić jej deklarację.
Ponieważ na razie nasze programy i tak mieszczą się w jednym pliku,
szczegóły odłożymy do
rozdziału o modułach programu .
Ta sama funkcja może być deklarowana wielokrotnie,
nawet w tym samym pliku.
Definicja natomiast powinna wystąpić tylko raz (wyjątkiem
są funkcje rozwijane, o których powiemy w dalszej części
tego rozdziału).
Oczywiście wszystkie deklaracje, jeśli jest ich kilka, muszą
być ze sobą zgodne, czyli definiować ten sam prototyp.
Definicja również musi być zgodna
z deklaracjami - to znaczy nagłówek funkcji musi być zgodny
z prototypem. Jak mówiliśmy, ta zgodność nie musi dotyczyć
nazw parametrów formalnych funkcji, które w ogóle nie mają
znaczenia w deklaracjach i do prototypu nie należą.
Nagłówek
określa
prototyp, czyli zewnętrzne własności funkcji. W najprostszej
postaci wygląda on tak:
Typ Nazwa ListaParam
gdzie
Typ określa typ wartości zwracanej (przed nią mogą
wystąpić modyfikatory, o których powiemy w dalszej części
rozdziału),
Nazwa oznacza nazwę funkcji, a
ListaParam listę parametrów
formalnych.
Typ wartości zwracanej.
Musi to być typ wbudowany (jak
int,
char, ...),
typ zdefiniowany przez użytkownika, typ pochodny
(
int*,
Osoba& itd.), albo wreszcie typu
void. Jeśli typem zwracanym nie jest
void,
to funcję nazywamy funkcją rezultatową.
Jeśli jest to
void, to
funkcja w ogóle nie zwraca żadnej wartości;
taką funkcję nazywamy funkcją bezrezultatową.
Choć kompilatory C/C++
dopuszczają brak jakiegokolwiek specyfikatora typu zwracanej
wartości (domniemywany jest wtedy typ
int), to nie
należy nigdy z tej możliwości korzystać: zawsze
określajmy jawnie typ wartości zwracanej. Typ wartości
zwracanej zwany jest też po prostu
typem funkcji.
Typem funkcji nie może być typ tablicowy i nie może nim
być typ funkcyjny; może to natomiast być typ wskaźnikowy
- w szczególności wskaźnik może wskazywać na tablicę
lub funkcję - lub typ odnośnikowy (referencyjny).
Nazwa.
Nazwa może być dowolna, byle nie kolidowała z którymś
ze słów kluczowych, składała się tylko z liter, cyfr i
znaków podkreślenia. Nie może jednak zaczynać się od
cyfry.
Lista parametrów formalnych.
Lista ta jest ujętą w okrągłe nawiasy
listą oddzielonych przecinkami deklaracji pojedynczych
parametrów funkcji
w postaci
(typ1 nazwa1, typ2 nazwa2).
Nie wolno stosować deklaracji zbiorczych:
(typ nazwa1, nazwa2) - nie byłoby wtedy wiadomo,
czy
nazwa2 jest drugim parametrem typu
typ,
czy typem następnego parametru, który może
sam nie mieć nazwy (patrz niżej). Nazwy wszystkich
parametrów muszą być różne.
Nazwy parametru można w ogóle nie podawać w
deklaracjach; w definicjach również można nazwę pominąć,
jeśli z argumentu wywołania skojarzonego z tym parametrem
funkcja w ogóle nie korzysta, jak to często bywa
w czasie tworzenia funkcji w jej wstępnych wersjach.
Lista parametrów może być pusta; obejmujących ją
nawiasów pominąć jednak nie można. Jeśli lista
parametrów jest pusta, to zalecane jest jawne zaznaczenie tego
przez wpisanie wewnątrz nawiasów słowa kluczowego
void. Nie jest to jednak konieczne.
Pamiętajmy też, że poprzedzenie nazwy typu słowem
kluczowym
const odpowiada zmianie typu:
typ
int* i typ
const int* to dwa
różne typy. Jeśli typem parametru jest na przykład
const int*, to kompilator nie zgodzi się na zmianę
wartości zmiennej wskazywanej poprzez nazwę wskaźnika
przekazanego jako argument (co funkcja normalnie może
zrobić; przekazana jej została, co prawda, kopia adresu, ale
sam adres odpowiada oryginalnej zmiennej z funkcji
wywołującej).
Część nagłówka funkcji składająca się z nazwy funkcji i listy
typów jej parametrów (bez nazw tych parametrów i bez typu wartości
zwracanej) nazywa się czasem
jej sygnaturą.
Tak więc na przykład sygnaturą funkcji o prototypie
double fun(double x, char* nap);
jest
fun(double, char*)
Definicja
funkcji składa się z takiego samego nagłówka, tyle
że teraz nie kończymy go średnikiem, tylko umieszczamy
zaraz za nim, ujętą w nawiasy klamrowe, treść (ciało) funkcji,
czyli sekwencję instrukcji do wykonania. Jeśli w ciele funkcji
chcemy korzystać z argumentu przekazanego poprzez parametr funkcji,
to oczywiście ten parametr musi mieć nazwę (którą w nagłówku
deklaracji mogliśmy pominąć). W programie może występować tylko
jedna definicja funkcji, choć wiele deklaracji
(one definition rule).
Wyjątkiem są funkcje rozwijane,
które mogą być definiowane wielokrotnie
(patrz podrozdział o funkcjach rozwijanych ).
Ciało funkcji może być traktowane jak wnętrze instrukcji
grupującej (złożonej). Do zakresu tej instrukcji
grupującej należą również deklaracje zmiennych
lokalnych opisane przez specyfikacje parametrów funkcji.
Zmienne definiowane w ciele funkcji będą w czasie jej wykonywania
lokalne - po wykonaniu funkcji są one usuwane. Zmiennymi
lokalnymi są również zmienne wyspecyfikowane jako parametry
formalne funkcji: będą one zainicjowane wartościami argumentów
wywołania, a po zakończeniu wykonywania funkcji - usunięte.
Zmienne lokalne funkcji (w tym te deklarowane przez specyfikacje
parametrów w nagłówku funkcji) w żaden sposób nie kolidują ze
zmiennymi o tej
samej nazwie w innych funkcjach. Mogą natomiast
przesłaniać nazwy zmiennych globalnych, zadeklarowanych poza
funkcjami i klasami. Jeśli
tak jest, to niekwalifikowana nazwa występująca w ciele funkcji
odnosi się zawsze do zmiennej lokalnej, natomiast dostęp do zmiennej
globalnej o tej nazwie mamy poprzez operator zasięgu - czterokropek
(patrz rozdział o zasięgu i widzialności zmiennych).
Nie wolno definicji funkcji zagnieżdżać, to znaczy nie
można w ciele jednej funkcji definiować innej funkcji
(co jest dozwolone w innych językach, jak
Pascal
czy Fortran 90/95).
Deklaracje i definicje funkcji nie są instrukcjami wykonywalnymi
Zatem kolejność, w jakiej je piszemy, nie ma znaczenia, dopóki
spełniony jest warunek, że deklaracja/definicja poprzedza leksykalnie
instrukcje, w których funkcja jest wywoływana.
Dalej: 11.3 Wywołanie funkcji
W górę: 11. Funkcje
Wstecz: 11.1 Wstęp
T.R. Werner, 24 wrzesnia
2007, godz. 22:51
Wyszukiwarka
Podobne podstrony:
node65node65node65node65 E5AKNKBOQD67UTC5JZMHBH35B2SWFLG4GTCYWWYnode65 LWVDB5QFAZ63ZNPH4WFW7AL5LEENB4RW7EYK4AInode65 4DADQA7N4UY4L6Z33USWQPL6V5VZNDMP7NRQ47Ynode65 1więcej podobnych podstron