Preopterećenje funkcija
Preopterećenje funkcija (en. function overloading) ili preopterećenje metoda (en. method overloading) mogućnost je programskog jezika da stvori više podprograma koje nose isto ime ali ponašaju se drukčije, ovisno o kontekstu njihova poziva. Pozivi k preopterećenoj funkciji pokrenut će specifičnu implementaciju funkcije prikladnu kontekstu poziva. To omogućuje funkcijama izvršavanje različitih naredbi ovisno o tome kako su pozvane.
Pojmovi funkcija i metoda odnose se na doseg potprograma, funkcije su dio glavnog koda, dok su metode implementacije ponašanja nekog objekta definiranog klasom. Preopterećenja rade jednako u oba slučaja.
Primjerice, deklaracijamaizvrši()
i izvrši(objekt o)
stvorene su dvije preopterećene funkcije. Kako bi se pozvala druga funkcija, potrebno je proslijediti objekt kao parametar. Ako parametar nije pružen, poziva se prva funkcija. Česta pogreška bila bi dodijeliti zadanu vrijednost objektu u drugoj funkciji, što bi rezultiralo pogreškom en. ambiguous call – „dvosmislen poziv” jer kompilator ne bi znao koju metodu je potrebno pozvati
Drugi primjer bila bi funkcija za ispis ispiši(objekt o)
koja izvršava drukčiji kod ovisno o tome ispisuje li primjerice tekst ili sliku. Takva bi se funkcija u implementaciji preopteretila kao ispiši(tekst t)
i ispiši(slika s)
. U programskom kodu bilo bi nužno samo pozvati ispiši(x)
, a kompilator bi sam odlučio koju funkciju treba izvršiti, ovisno o tome kojem tipu podatka varijabla x pripada.
Jezici koji podržavaju preopterećenje funkcija su, između ostalih:
- Apex
- C++
- C#
- Swift
- Fortran
- Kotlin[1]
- Java
- PostgreSQL[2] and PL/SQL[3]
- Scala
- TypeScript
- Wolfram Language
- Elixir
- Nim[4]
- Isto ime funkcije koristi se za više od jednu definiciju funkcije
- Istoimene funkcije moraju se razlikovati po broju (prvi primjer) ili po tipu (drugi primjer) parametara
Preopterećenje je obično asocirano sa statičkim programskim jezicima (koji zahtjevaju da svaka varijabla ima određen tip podatka) koji pri pozivu funkcije provjeravaju tipove parametara koje funkcija prima.
Preopterećenje je oblik statičkog polimorfizma, u kojem pri kompilaciji programa neki algoritam među nekoliko istoimenih funkcijskih potpisa (deklaracija) traži najbliže podudaranje između deklariranog (formalnog) i pozvanog tipa parametara. Detalji i implementacija ovog algoritma razlikuju se po jezicima.
Preopterećenje funkcija ne treba zamijeniti sa oblicima polimorfizma. Polimorfizam koristi nadređivanje metoda.
Primjer: Preopterećenje funkcija u C++
#include <iostream>
int Volumen(int s) { // Volumen kocke.
return s * s * s;
}
double Volumen(double r, int h) { // Volumen valjka
return 3.1415926 * r * r * static_cast<double>(h);
}
long Volumen(long l, int b, int h) { // Volumen kvadra
return l * b * h;
}
int main() {
std::cout << Volumen(10); // kocka
std::cout << Volumen(2.5, 8); // valjak
std::cout << Volumen(100l, 75, 15); // kvadar
}
U gornjem primjeru, svaki poziv funkcije volumen odabire funkciju koja odgovara pozivu po broju i tipu parametara.
Konstruktor je klasna metoda odgovorna za stvaranje instance pojedinog objekta. Neki programski jezici dopuštaju preopterećenje konstruktora. S obzirom da konstruktori obično nose ime klase koju instanciraju, ako želimo definirati više konstruktora, potrebno je konstruktor pereopteretiti definiranjem dodatnih deklaracija (potpisa).
U C++-u, zadani konstruktori ne primaju parametre, već instanciraju članske varijable na odgovarajuće zadane vrijednosti, što je obično nula za brojčane vrijednosti i prazan string za tekstualne vrijednosti.[5]
Primjerice, zadani konstruktor za račun u restoranu napisan u C++ možda zadano postavlja napojnicu na 15%:
Racun()
: napojnica(0.15), // postotak
iznos(0.0)
{ }
Nedostatak ovog pristupa je da se ovako kreirani račun mora promijeniti koristeći dvije naredbe:
Racun cafe;
cafe.napojnica = 0.10;
cafe.iznos = 4.00;
Preopterećenjem konstruktora možemo i napojnicu i iznos proslijediti kao parametre pri kreiranju objekta Račun. Novi se konstruktor piše uz već postojeći zadani konstruktor. Koji će se konstruktor pozvati ovisi o broju parametara koji se proslijeđuje novostvorenom objektu Računa: nula parametara poziva zadani kostruktor sa napojnicom od 15%, dok unos dva parametra poziva drugi konstruktor, koji odmah mijenja postotak napojnice i iznos računa.
Racun(double napojnica, double iznos)
: napojnica(napojnica),
iznos(iznos)
{ }
Ako želimo stvoriti račun sa drukčijom vrijednošću napojnice i definiranim iznosom, sada to možemo učiniti u jednom koraku:
Racun cafe(0.10, 4.00);
Ovakvo postupanje povećava efikasnost programa i smanjuje duljinu programskog koda.
Preopterećenje konstruktora može se koristiti i kako bi se unošenje nekih članskih varijabli učinilo obveznim. Ako se ogranići pristup zadanom konstruktoru (ili se, od C++11, isti izbriše), za kreiranje objekta mora se koristiti neki preopterećeni kostruktor, koji će, ovisno o implementaciji, zahtjevati unošenje barem jedne članske varijable.
Dva ponašanja kompliciraju preopterećenje funkcija: prikrivanje imena (en. name masking) i implicitno pretvaranje tipova.
Ako se funkcija delkarira u nekom dosegu, i zatim se u unutarnjem dosegu koji obuhvaća prvi deklarira nova funkcija istog imena, postoje dva moguća nadređenja:
- unutarnja deklaracija uvijek maskira vanjsku deklaraciju (neovisno o potpisu funkcije), ili
- učitavaju se i vanjska i unutarnja funkcija, a unutarnja maskira vanjsku samo ako obje imaju isti potpis
Jezik C++ podržava samo prvo nadređenje, odnosno funkcije različitih dosega (primjerice iz deriviranih klasa) ne mogu se preopteretiti.[6] Kako bi isti kod učitao funkcije različitih dosega, potrebno je eksplicitno učitati funkcije vanjskog dosega u unutarnji doseg ključnom riječju using
.
Implicitna pretvorba parametarskih tipova komplicira preopterećenje funkcija: ako se tipovi parametara ne podudaraju u potpunosti sa potpisom neke od funkcija, ali se mogu podudarati nakon implicitne pretvorbe, rezultat funkcije može biti neočekivan ovisno o načinu pretvorbe koju kompilator odabere.
Komplikacije mogu uključivati jedna drugu, pa tako nepotpuno poklapanje potpisa deklariranog u unutarnjem dosegu može prikriti egzaktno poklapanje funkcije u vanjskom dosegu.[6]
Primjerice, kako bi naslijeđena klasa sa nadređenom metodom koja prima decimalan broj (double) ili cijeli broj (int) primila int iz bazne klase, potrebno je napisati:
class B {
public:
void F(int i);
};
class D : public B {
public:
using B::F;
void F(double d);
};
Naredba using osigurava da će se u deriviranoj klasi dogoditi preopterećenje, pa će se izvesti metoda F bazne klase.
Ako izostavimo naredbu using
, int parametar bit će proslijeđen metodi F derivirane klase, koja će ga pretvoriti u double.
- Apstrakcija (računarstvo)
- Konstruktor (programiranje)
- Deklaracija (računarstvo)
- Nadređivanje metoda
- Objektno orijentirano programiranje
- Preopterećenje operatora
- ↑ Kotlin language specification. kotlinlang.org
- ↑ 37.6. Function Overloading. PostgreSQL Documentation (engleski). 12. kolovoza 2021. Pristupljeno 29. kolovoza 2021.
- ↑ Database PL/SQL User's Guide and Reference. docs.oracle.com (engleski). Pristupljeno 29. kolovoza 2021.
- ↑ Nim Manual. nim-lang.org (engleski)
- ↑ Chan, Jamie. 2017. Learn C# in One Day and Learn It Well. Revised izdanje. str. 82. ISBN 978-1518800276
- ↑ a b Stroustrup, Bjarne. Why doesn't overloading work for derived classes?
- Meyer, Bertrand. Listopad 2001. Overloading vs Object Technology (PDF). Eiffel column. Journal of Object-Oriented Programming. 101 Communications LLC. 14 (4): 3–7. Pristupljeno 27. kolovoza 2020.
|