Cikk virtuális funkciók

Virtuális funkciók. Mi ez? 1. rész

1. rész A virtuális funkciók általános elmélete

Ha megnézzük ennek a cikknek a címét, akkor azt gondolhatja: "Hmm, ki nem tudja, milyen virtuális funkciók vannak!" Ugyanaz. "Ha így van, akkor biztonságosan eldobhatja az olvasást.

És azok számára, akik csak most kezdik megérteni a C ++ bonyolultságait, de már rendelkezésükre áll az eredetismeret az örökségről. és hallottam valamit a polimorfizmusról. van közvetlen értelme olvasni ezt az anyagot. Ha megérted a virtuális funkciókat, akkor megkapod a kulcsot a sikeres objektumorientált tervezés titkainak kibontakoztatásához.

Úgy döntöttem, hogy az anyagot 3 részre osztom.
Próbáljuk ki az első részben, hogy megértsük a virtuális funkciók általános elméletét. Nézzük meg alkalmazásuk második részét (és erõsségüket és erejüket!) Néhány többé-kevésbé életmódban. Nos, a harmadik részben beszéljünk olyan dolgokról, mint a virtuális destruktorok.

Tehát mi az?

Kezdjük azzal, hogy emlékezzünk arra, hogy a klasszikus C programozásban egy adatobjektumot átadhatunk egy függvénynek. Ebben nincs semmi bonyolult, csak akkor kell beállítani az átvitt objektum típusát, amikor a funkciókódot írja. Vagyis az objektumok viselkedésének leírásához előzetesen ismerni és leírni a típusukat. Az OOP ereje ebben az esetben abban nyilvánul meg, hogy virtuális függvényeket írhat, hogy maga az objektum határozza meg, hogy milyen funkciót kell hívnia a program végrehajtása során.

Más szóval, a virtuális funkciók segítségével az objektum maga határozza meg viselkedését (saját cselekvéseit). A virtuális funkciók használatának technikáját polimorfizmusnak nevezik. Szó szerint a polimorfizmus sok formát ölt. Egy programban lévő objektum ténylegesen nem egy osztályt képvisel, hanem sok különböző osztályt, ha az öröklési mechanizmus kötődik egy közös alaposztályhoz. Nos, ezeknek az osztályoknak a tárgyak viselkedése a hierarchiában természetesen más lesz.

Nos, most a lényegre!

Mint ismeretes, a C ++ szabályok szerint egy mutató egy alaposztályhoz utalhat erre az osztályra vonatkozó objektumra, valamint bármely más osztály objektumára, amely az alap osztályból származik. Ennek a szabálynak a megértése nagyon fontos. Nézzünk egy bizonyos A, B és C osztályok egy egyszerű hierarchiáját. És lesz egy alaposztályunk, B származik az A osztályból és C származik B-ból. A tisztázáshoz lásd az ábrát.

A programban az ilyen osztályok tárgyai például ilyen módon deklarálhatók.


E szabály szerint egy A típusú mutató hivatkozhat ezekre a három objektumra. Vagyis ez így lesz:


De ez nem helyes:


Bár a point_to_Object típus A *, nem C * (vagy B *) típusú, C (vagy B) típusú objektumokra hivatkozhat. Talán a szabály érthetőbb lesz, ha a C objektumot különleges A tárgynak tartja. Hát például egy pingvin egy különleges madár, de még mindig madár marad, bár nem repül. Természetesen az objektumok és mutatók közötti kapcsolat csak egy irányban működik. A C típusú tárgy egy különleges A objektum, de az A tárgy nem különleges C tárgy. Visszatérve a pingvinekhez biztonságosan elmondható, hogy ha minden madár különleges pingvin volt - egyszerűen nem tudtak repülni!

A osztály
nyilvános:
virtuális üres v_funkció (üres); // a függvény az A osztály viselkedését írja le
>;


Egy virtuális függvény deklarálható paraméterekkel, visszaadhatja az értéket, mint bármely más funkciót. Egy osztály kijelölheti annyi virtuális funkciót, amennyire szüksége van. És lehetnek az osztály bármelyik részében - zárt, nyitott vagy védett.

Ha a B. osztályban, ami az A osztályból származik, le kell írnia valamilyen más viselkedést, akkor ismét virtuális függvényt (v_function () nevezhet ki).

B osztály: nyilvános A
nyilvános:
virtuális üres v_funkció (void); // a helyettesítő függvény a
// a B osztály új viselkedése
>;


Ha egy olyan virtuális függvényt definiálunk egy olyan osztályban, mint a B, amelynek neve ugyanúgy, mint az ősosztály virtuális függvénye, akkor ezt a funkciót helyettesítési függvénynek nevezzük. A virtuális függvény v_funkció () a B helyettesíti az ugyanolyan névvel rendelkező virtuális függvényt az A. osztályban. Valójában minden valamivel bonyolultabb, és nem jön le a nevek egyszerű egyezésére. De még többet erről a későbbiekben, a "Néhány alkalmazhatóság finomsága" című részben.
Most, a legfontosabb dolog!

Térjünk vissza az A * típusú point_to_Object mutatóra, amely a B * típusú objektum object_V-re utal. Vessünk egy közelebbi pillantást az utasításra, amely a v_function () virtuális függvényt hívja az object_to_Object felé mutató objektumra.

Nos, mit ad nekünk?

Ideje látni - és mit adnak nekünk a virtuális funkciók? A virtuális funkciók elméletét általánosságban tekintettük. Itt az ideje, hogy fontolja meg valódi helyzetet, ahol meg lehet érteni a tantárgy gyakorlati jelentőségét a programozás valós világában.

Egy klasszikus példa (tapasztalatom szerint - a C ++ -re vonatkozó összes irodalom 90% -ában), ami ebből a célból vezet - grafikai programot ír. Az osztályok hierarchiája úgy van kialakítva, mint a "dot-gt; line-gt; lapos-gt; háromdimenziós alak". És egy virtuális függvényt tekintünk, mondjuk, Draw (), ami mindent rajzol. Unalmas!

Nézzünk egy kevésbé tudományos, de mégis grafikus példát. (Klasszikusok, hol hagyják el?). Próbáljuk hipotetikusan megfontolni egy olyan elvet, amely beágyazható egy számítógépes játékba. És nem csak egy játék, de alapja bármely (függetlenül attól, 3D vagy 2D, hűvös vagy úgy) szórakoztató. Lövés, más szóval. Nem vagyok vérszomjas az életben, de bűnös vagyok, néha szeretem lőni!

Tehát úgy döntöttünk, hogy egy hűvös lövőt készítünk. Mi szükséges elsősorban? Természetesen fegyverek! (Nos, nem az első.) Nem számít, attól függően, hogy melyik témát alkotjuk, ilyen fegyverekre lesz szükség. Lehet, hogy ez egy egyszerű klubból egy körtest. Az arquebuszból a gránátvetõhöz. Vagy talán egy robbanóból egy szétesőhöz. Hamarosan látni fogjuk, hogy ez nem fontos.

Nos, mivel olyan sok lehetőség van, szükség van egy alap osztályra.

osztályú fegyver
nyilvános:
. // lesz adat tagok, amelyek leírhatók, például, mint
// egy klub vastagsága, és a gránátok száma gránátrakóban
// ez a rész nem számunkra fontos

virtuális void Use1 (void); // általában - a bal egérgombot
virtuális üres Use2 (void); // általában - a jobb egérgombot

// további tagadatok és módszerek lesznek
>;


Anélkül, hogy bemennénk az osztály részleteit, azt mondhatjuk, hogy a legfontosabbak a Use1 () és a Use2 () függvények, amelyek leírják a fegyver viselkedését (vagy használatát). Ebből az osztályból bármiféle fegyvert hozhat létre. Új adat tagok (mint például a patronok száma, a tűz aránya, az energia szintje, a pengehossza stb.) És az új funkciók hozzáadásra kerülnek. A Use1 () és a Use2 () függvények újradefiniálásával leírjuk a fegyverek használatának különbségét (egy késsel ez lehet egy ütés és dobás, a gépfelvételhez és a robbanáshoz).

A fegyvergyűjteményt valahol el kell tárolni. Nyilvánvalóan a legegyszerűbb módja annak, hogy megszervezzenek olyan mutatókat, mint a Weapon *. Az egyszerűség kedvéért tegyük fel, hogy ez a fegyverek globális tömbje, 10 fegyver számára, és az összes mutató nullázódik az elején.

Fegyver * fegyverek [10]; // mutatók csoportja a Weapon típusú objektumokhoz


A program elején a dinamikus objektumok-típusú fegyverek létrehozásával hozzá fogunk adni mutatókat a tömbben.

Annak meghatározásához, hogy mely fegyver használatban van, meg kell adnunk a tömb változó indexét, amelynek értékét a választott fegyver típusától függően megváltoztatjuk.


Ezeknek az erőfeszítéseknek köszönhetően a játék fegyvereinek használatát leíró kód például így néz ki:

ha (LeftMouseClick) karok [TypeOfWeapon] -gt; Use1 ();
Egyéb fegyverek [TypeOfWeapon] -> Use2 ();


Ez minden! Olyan kódot hoztunk létre, amely leírja a lövöldözés-háborút még mielőtt eldönti, hogy milyen típusú fegyvereket fog használni. Ennél több. Még egyáltalán nem léteznek valódi fegyverek! További (néha nagyon fontos) előny - ez a kód külön összeállítható és a könyvtárban tárolható. A jövőben (vagy egy másik programozó) új fegyvereket tud készíteni a Weapon-ból, menteni őket a # 91; # fegyverzetben és felhasználva. Nem kell újrafordítani a kódot.

Különösen vegye figyelembe, hogy ez a kód nem feltétlenül adja meg pontosan a 91-es számú fegyver által hivatkozott adatobjektumok típusát, csak akkor, ha a Weaponból származik. Az objektumokat futás közben határozzák meg. milyen funkciót használni () kell hívni.

Néhány alkalmazás finomsága

Töltsünk el egy kis időt a virtuális funkciók cseréjére.

Térjünk vissza az elejére - az A, B és C unalmas osztályokra. A C osztály pillanatnyilag a hierarchia legvégén áll, az örökség sorának végén. A C. osztályban pontosan meghatározhatja a helyettesítő virtuális funkciót is. És a virtuális kulcsszó használata nem szükséges, mert ez az utolsó osztály az örökség sorában. Funkció, és így működni fog és virtuálisan lesz kiválasztva. De! De ha a C. osztályból egy D osztályt akarunk leiratkozni, és még a v_funkció () függvény viselkedését is megváltoztatjuk, akkor semmi sem fog származni. Ehhez a C osztályban a v_function () függvényt virtuálisnak kell nyilvánítani. Ezért a szabály, amely a következőképpen fogalmazható meg: "egyszer virtuális - mindig virtuális!". Vagyis a virtuális kulcsszó jobb, ha nem dobja el - hirtelen jól jön?

Egy másik finomság. Egy származtatott osztályban nem definiálhat ugyanannak a névnek és ugyanazon paraméterkészletnek a függvényét, de más típusú visszatérési értékkel, mint az alap osztály virtuális függvénye. Ebben az esetben a fordító kísért a program összeállításának szakaszában.

Tovább. Ha a származtatott osztályba tartozó függvényt ugyanolyan névvel és visszatérési típussal írja be, mint az alaposztály virtuális függvényét, de más paraméterekkel, akkor ez a származtatott osztályfüggvény többé nem lesz virtuális. Még ha a virtuális kulcsszóval is elkíséri, akkor nem az várható. Ebben az esetben, ha az egérmutatót az alaposztályhoz használjuk, ennek a mutatónak bármely értéke esetén az alap osztály funkcióra hívást fogunk végrehajtani. Ne feledkezzen meg a túlterhelési funkciókról! Ezek csak más funkciók. Kapsz egy teljesen más virtuális funkciót. Általánosságban elmondható, hogy ezek a hibák nagyon finomak, mivel az írás mindkét formája teljesen elfogadható, és a fordítói diagnosztika reménye ebben az esetben nem szükséges.

Ezért egy másik szabály. Behelyettesítve virtuális szükséges funkciókat teljes egyezés paraméter típusok, funkció nevét és típusát visszatérési értéke a bázis és a származtatott osztályokban.

És még egy. Virtuális funkció csak akkor lehet nem-statikus komponens funkció az osztály. Virtuális nem lehet globális funkciót. A virtuális függvény lehet nyilvánítani barát (

) A másik osztályba. De felhasználóbarát funkciók lesz szó, hogyan, vagy hogy egy másik cikkben.

Ez minden, ebben az időben.

A következő részben látni fog egy teljesen működőképes példa egy egyszerű program, amely megmutatja az összes pontot, hogy megbeszéltük.

Mikor ezt írom, az alábbi könyvek:

Ha bármilyen kérdése van - írj, mi meg fogjuk vizsgálni.

Sergey Malyshev (aka Mikhalych).

Kapcsolódó cikkek