Röviden egy két mondatban le tudnátok írni, hogy mi a különbség a függvényfelülírás és a virtuális függvények között?
Köszönöm :)
Legyetek szívesek ne linket küldeni,hanem magyarázzátok el röviden.
a virtuális függvény az késői kötés. azzal csak jelölöd hogy egy gyerekosztályba kifogod dolgozni azt a függvényt (override)
a függvényfelülírással pedig csak a hatáskörökkel játszadozol:) és felülírod egy másik osztályban mint a neve is mutatja
Egy konkrét szituáció:
Van mondjuk egy tömböd, amibe különböző fajta widget-eket tartasz (pontosabban, különböző fajta widget-ekre mutató pointereket)
Widget *widgettömb[3];
... feltöltés ...
Ebben a tömmben mondjuk van egy gomb-widget, egy legördülőlista-widget, meg valami szövegmező-widget.
Most végigmegyek a tömbön, és az összes widget-et megjelenítem:
for (int i = 0; i < 3; i++)
. , . , . , widgettömb[i]->show();
és a GUI-felületen máris megjelenik egy előkészített nyomógomb, egy legördíthető lista, meg egy szövegmező emive gépelni lehet.
Az az érdekes, hogy a tömbben ,,egységesen'' Widget típusú objektumok(ra mutató pointerek) vannak, tehát nincs gond azzal, hogy milyen tipusú legyen a tömb: a tömb ilyen értelemben ,,homogén'', mindegyik eleme Widget(re mutató pointer). Tehát nem kell ilyen szkriptnyelv-féle heterogén tömbökkel bűvészkedni, típusellenőrzés szempontjából a tömb típusa világos. Viszont ahogy végigmegyek az tömb elemein, és rendre meghívom az egyes elemek show() metódusát, minden egyes elem másképp viselkedik. Egy gomb show() metódusa nyilván egész máshogy rajzolja ki magát a képernyőre, mint egy legördülő lista show() metódusa, vagy egy szövegmezőé.
Szóval van egy ,,közös'' Widget osztály, és ennek van három leszármazott osztálya: Gomb, LegördülőLista, Szövegmező. A Widget a közös szülőosztály, tehát nyugodtan tehetem mindhárom különféle típusú widgetet egy tömmbe: úgy deklarálom a tömböt, hogy a tömb Widget-eket tartalmat. Viszont a tömböt valójában mégis egészen különféle (al)típusú widget-objektumokkal töltjük fel.
A Widget osztályban van deklarálva egy show() tagfüggvény, ehhez azonban vagy nincs definiálva konkrét művelet, vagy pedig csak viszonylag érdektelen művelettel van definiálva (mondjuk valami érdektelen loggolási vagy debuggolási dolog). Tehát nincs valódi ,,egységes'' show(). Az egyes leszármazott osztályok öröklik a Widget szülőosztály show() tagfügvényét, de ezt persze egyben felül is definiálják a megfelelő konkrét megjelenítési művelettel: a nyomógomb nyilván valami négyszögletes-meg feliratos ,,nyomógombrajz'' formájában rajzolja ki magát, a legördülő lista legörgethető lista rajzaként, a szövegmező pedig gépelhető kurzor meg hely kirakásával jeleníti meg magát.
Most képzeld el a programot a fordító szemszögéből! Ő csak annyit lát, hogy van egy widgettömb, de azt ő előre nem tudja, hogy az a widgettömb milyen widgetfajtákkal lesz az majd feltöltve. Ő csak annyit tud, hogy ebben a widgettömbben nyilván widget típusú objektumok(ra mutató pointerek) lesznek, és ezeken majd végigmenve az elemeknek sorra meg lesz hívva a show() metódusuk. Azt viszont ő előre nem tudja lefordítani, hogy miféle konkrét rajzoló algoritmus is lesz meghívva, hiszen az majd a konkrét fajta widgettől függ. A fordítónak tehát nem valami konkrét rajzoló műveletet fog lefordítani a tömb feldolgozását végző kódrészletbe, hanem a fordító valahogy tud róla, hogy ha Widget típusú objektummal találkozik, akkor az valamelyik valamelyik leszármazott widget-osztályhoz is tartozhat, hogy melyikhez, azt úgyis majd csak futási időben lehet majd eldönteni, tehát azt is futási időben kell előkeresni, hogy melyik leszármazott, felüldefiniált show() rajzműveletet kell majd meghívni.
Mindezt valóban csak futási időben lehet eldönteni (fordítási időben általában még nem lehet előre tudni, hogy egy tömb milyen fajta widgetekkel lesz futáskor majd feltöltve, hiszen akár a felhasználó választása szerint is fel lehet tölteni egy widgettömböt)
A lényeg, hogy ha külön nem szólok a fordítónak, hogy én a Widget osztály show() tagfüggvényét virtuálisan szeretném, akor ő ,,favágó'' módon a Widget osztály ,,közös'' show() műveletét fordítja majd be a kódba. C++ ugyanis ez az alapértelmezés, és külön kell kérni (a virtuálisként való deklarással), hogy ne így favágó módon tegye.
Tehát ha a Widget osztály ,,saját'' show() függvényét nem deklarálnám ,,virtual''-nak, akkor a fordító olan kóddal fordítaná le a widgettömbön-végigmenő ciklusomat, hogy ciklus úgy menne végig a widgettömbön, hogy a ,,közös'' Wiget-osztályban definiált show() műveletet hajtja végig az egyes elemekre, tekintet nélkül a korét widgetfajtára, tehát ugyanúgy a gombra is, a legördülő listára is, a szövegmezőre is, mindenféle widgetre, ami csak a tömmben van, akármilyen fajta konkrét widgettípusról is van szó.
Persze egységes ,,widgetrajzoló'' függvény értelmesen nem képzelhető el, így a Widget osztály ,,saját'' show() művelete valószínűleg legfeljebb csak valami loggolási, debuggolási célú csonkjellegű műveletként van definiálva.
Ezért ilyen fordítás esetén a ciklus úgy menne végig a tömbön, hogy hiába vannak benne a legváltozatosabb widgetek (gomb, legördülő lista , szövegmező), mindegyikre csak valami unalmas loggolási csonkszerű művelet történne, és a képernyőn nem a konrét alakzatok jelennének meg a maguk fajtája szerint elvárható módján.
A lényeget még ebből tudtam megérteni valamennyire megérteni
#include <iostream>
class Widget {
public:
, . , // 1. eset: ,virtual' kulcsszók kikommentezve
, . , // 2. eset: kiírjuk a ,virtual' kulcsszót, kiszedjük a kommentjeleket
, . , /* virtual */ void show()
, . , {
, . , , . , std::cout << "Generálszósz loggolási szöveg: ,,wiget''-et ,,rajzolnánk'' (konrétum nélkül)" << std::endl;
, . , }
};
class Button: public Widget {
public:
, . , void show()
, . , {
, . , , . , std::cout << "Gomb rajzolása" << std::endl;
, . , }
};
class TextField: public Widget {
public:
, . , void show()
, . , {
, . , , . , std::cout << "Szövegmező rajzolása" << std::endl;
, . , }
};
int main(int argc, char *argv[])
{
, . , Widget *widget = new Widget;
, . , widget->show();
, . , // Nincs különbség az 1. és 2. eset közt: a generál-logszöveg jelenik meg
, . , Button *button = new Button;
, . , button->show();
, . , // Nincs különbség az 1. és 2. eset közt: gomb rajzolása történik
, . , delete widget;
, . , delete button;
, . , widget = new Button;
, . , widget->show();
, . , // 1. és 2. eset közt lényeges külöbség van:
, . , // 1.: ,virtual' elhagyása esetén az általános Widget loggoló generálszósz show()-ja hívódik meg
, . , // 2.: ,virtual' kiírása esetén button-ként jelenik meg, a Button-hoz tartozó specifikus show()-ja hívódik meg
, . , delete widget;
, . , return 0;
}
Látszik hogy az igazán érdekes eset az a harmadik példa: vagyis amikor egy objektum fordítási időben megállapítható típusa nem azonos a futási időben ,,aktualizálódó'' ,,tényleges'' típusával.
A virtual kulcsszó használatával vagy elhagyásával egyértelműsítjük a fordító számára ezt a kétértelmű helyzetet
1)
virtual kulcsszó elhagyása a főosztálybeli deklarációban:
a fordító ne törödjék azzal, hogy esetleg az objektum futási időben ,,aktualizálódó'' ,,tényleges'' típusa nem azonos az objektumnak a már fordítási időben is megállapítható típusával: nyugodtan fordítsa be a már fordítási időben is megállapítható típushoz tartozó metódus törzsét
2)
virtual kulcsszó kiírása a főosztálybeli deklarációban:
a fordító igenis számítson arra az eshetőségre, hogy esetleg az objektum futási időben ,,aktualizálódó'' ,,tényleges'' típusa nem azonos az objektumnak a már fordítási időben is megállapítható típusával: ennek megfelelően a fordító nehogy befordítsa a már fordítási időben is megállapítható típushoz tartozó metódus törzsét, hanem úgy fordítson, hogy majd csak a kód futási időben döntsön az objektum ,,tényleges'', futási időben aktualizálódó típusa felől, és futási időben történjék meg az a döntés is, hogy melyik show()-implementációt is kell meghívni.
Sokat emlegetitek a kései kötést, avagy hogy a rendszer a hívás pillanatában "dönt" arról, hogy mit is hívjon meg.
A C++-hoz hasonló nyelvek esetén ez a döntés nem egy bonyolult dolog: Minden konkrét osztály tartalmaz egy táblázatot, ami megadja, hogy egy adott virtuális függvény kódjához mi a pointer. A táblán belüli indexe a függvényeknek minden osztályhierarchiához azonos.
Vagyis nem virtuális függvény esetén a függvény hívásakor van egy pointer, ami közvetlenül az implementációra mutat. Virtuális függvény esetén a pointer egy táblának egy elemére mutat, ahonnan már a tényleges kód pointere kiolvasható.
Tehát az első esetben: p->kód, a másodikban p->vp->kód.
Ennyi.
Persze nyelvtől is függ. A dinamikus nyelveknél ez ugye nem működik.
Kapcsolódó kérdések:
Minden jog fenntartva © 2024, www.gyakorikerdesek.hu
GYIK | Szabályzat | Jogi nyilatkozat | Adatvédelem | Cookie beállítások | WebMinute Kft. | Facebook | Kapcsolat: info(kukac)gyakorikerdesek.hu
Ha kifogással szeretne élni valamely tartalommal kapcsolatban, kérjük jelezze e-mailes elérhetőségünkön!