Java-da erta va kech ulanish o'rtasidagi farqlar

Maqolada men inline funktsiyalari bilan ishlashda hammaga ma'lum bo'lmagan qanday xususiyatga duch kelishingiz mumkinligi haqida gapirdim. Maqolada bir nechta muhim sharhlar va ko'p sahifali munozaralar (va hatto bayramlar) paydo bo'ldi, ular inline funktsiyalardan umuman foydalanmaslik yaxshiroq ekanligidan boshlandi va standart C va boshqalar mavzusiga aylandi. C++ va boshqalar. Java vs. C# va boshqalar PHP va boshqalar. Haskell va boshqalar. ...

Bugun virtual funksiyalar navbati. Va, birinchidan, men darhol ta'kidlaymanki, mening maqolam (asosan, avvalgisi kabi) hech qanday tarzda to'liq deb da'vo qilmaydi. Va ikkinchidan, avvalgidek, bu maqola professionallar uchun emas. Bu C++ asoslarini allaqachon tushungan, lekin yetarlicha tajribaga ega bo'lmaganlar yoki kitob o'qishni yoqtirmaydiganlar uchun foydali bo'ladi.

Umid qilamanki, hamma virtual funktsiyalar nima ekanligini va ular qanday ishlatilishini biladi, chunki buni tushuntirish endi mening vazifam emas. Ishonchim komilki, RFry C++ haqidagi turkum maqolalarida ertami-kechmi ularga yetib boradi.

Agar ichki usullar haqidagi materialda afsona to'liq aniq bo'lmasa, unda buning aksi. Aslida, keling, "afsona" ga o'tamiz.

Virtual funktsiyalar va kalit so'z virtual
Ajablanarlisi shundaki, men bunga ishonadigan odamlarni tez-tez uchratardim va uchrashishda davom etaman (nima desam bo'ladi, men ham xuddi shunday edim) virtual kalit so'z funksiyani ierarxiyada faqat bitta darajali virtual qiladi. Keling, nima demoqchi ekanligimni misol bilan tushuntiraman:

  1. #o'z ichiga oladi
  2. #o'z ichiga oladi
  3. std::cout dan foydalanish;
  4. std::endl dan foydalanish;
  5. tuzilmasi A
  6. virtual ~A()()
  7. << "A::foo()" << endl; }
  8. << "A::bar()" << endl; }
  9. void baz() const ( cout<< "A::baz()" << endl; }
  10. B tuzilmasi: ommaviy A
  11. virtual void foo() const ( cout<< "B::foo()" << endl; }
  12. void bar() const ( cout<< "B::bar()" << endl; }
  13. void baz() const ( cout<< "B::baz()" << endl; }
  14. C tuzilmasi: ommaviy B
  15. virtual void foo() const ( cout<< "C::foo()" << endl; }
  16. virtual void bar() const ( cout<< "C::bar()" << endl; }
  17. void baz() const ( cout<< "C::baz()" << endl; }
  18. int main()
  19. cout<< "pA is B:" << endl;
  20. A * pA = yangi B;
  21. pA->foo();
  22. pA->bar();
  23. pA->baz();
  24. pA o'chirish;
  25. cout<< "\npA is C:" << endl;
  26. pA = yangi C;
  27. pA->foo(); pA->bar(); pA->baz();
  28. pA o'chirish;
  29. qaytish EXIT_SUCCESS;

Shunday qilib, bizda oddiy sinf ierarxiyasi mavjud. Har bir sinf 3 ta usulni belgilaydi: foo(), bar() va baz() . Keling, ko'rib chiqaylik noto'g'ri mantiq ta'siri ostida bo'lgan odamlar afsona:
pA ko'rsatkichi B tipidagi ob'ektga ishora qilganda, biz quyidagi natijaga ega bo'lamiz:

pA B:
B::foo() // chunki A asosiy sinfida foo() usuli virtual sifatida belgilangan
B::bar() // chunki A asosiy sinfida bar() usuli virtual sifatida belgilangan
A::baz() // chunki A asosiy sinfida baz() usuli virtual deb belgilanmagan

Agar pA ko'rsatkichi C tipidagi ob'ektga ishora qilsa, biz quyidagi natijaga ega bo'lamiz:
pA C:
C::foo() // chunki B asosiy sinfida foo() usuli virtual sifatida belgilangan
B::bar() // chunki B asosiy sinfida bar() usuli virtual sifatida belgilanmagan,
// lekin u A sinfida virtual sifatida belgilangan, biz foydalanayotgan ko'rsatkich
A::baz() // chunki A sinfida baz() usuli virtual deb belgilanmagan

baz() virtual bo'lmagan funksiyasi bilan hamma narsa aniq. Ammo virtual funktsiyalarni chaqirish mantig'ida muammo bor. O'ylaymanki, haqiqiy mahsulot quyidagicha bo'ladi:

pA B:
B::foo()
B::bar()
A::baz()

PA - C:
C::foo()
C::bar()
A::baz()

Xulosa: virtual funktsiya ierarxiya va kalit so'zning oxirigacha virtual bo'ladi virtual faqat birinchi marta "kalit" bo'lib, keyingi paytlarda dasturchilarga qulaylik yaratish uchun sof ma'lumot beruvchi funktsiyani bajaradi..

Nima uchun bu sodir bo'lishini tushunish uchun virtual funktsiya mexanizmi qanday ishlashini tushunishingiz kerak.

Erta va kech bog'lanish. Virtual funktsiyalar jadvali
Bog'lash - bu qo'ng'iroqni funktsiya chaqiruvi bilan taqqoslash. C++ da barcha funksiyalar sukut bo'yicha mavjud erta bog'lash, ya'ni kompilyator va bog'lovchi dastur ishga tushishidan oldin qaysi funktsiyani chaqirish kerakligini hal qiladi. Virtual funksiyalar mavjud kech bog'lash, ya'ni funktsiyani chaqirishda dasturni bajarish bosqichida kerakli tana tanlanadi.

Virtual kalit so'z bilan uchrashgandan so'ng, kompilyator ushbu usul uchun kech bog'lashdan foydalanish kerakligini ta'kidlaydi: birinchidan, u sinf uchun virtual funktsiyalar jadvalini yaratadi va dasturchiga yashiringan yangi a'zoni sinfga qo'shadi - bu jadvalga ko'rsatgich. . (Aslida, bilishimcha, til standarti virtual funktsiya mexanizmini qanday amalga oshirish kerakligini aniq belgilamaydi, lekin virtual jadvalga asoslangan dastur de-fakto standartga aylandi.) Ushbu isbot kodini ko'rib chiqing:

  1. #o'z ichiga oladi
  2. #o'z ichiga oladi
  3. struct Empty();
  4. struct EmptyVirt ( virtual ~EmptyVirt()() );
  5. struct NotEmpty (int m_i; );
  6. struct NotEmptyVirt
  7. virtual ~NotEmptyVirt() ()
  8. int m_i;
  9. struct NotEmptyNonVirt
  10. void foo() const()
  11. int m_i;
  12. int main()
  13. std :: cout<< sizeof (Empty) << std::endl;
  14. std :: cout<< sizeof (EmptyVirt) << std::endl;
  15. std :: cout<< sizeof (NotEmpty) << std::endl;
  16. std :: cout<< sizeof (NotEmptyVirt) << std::endl;
  17. std :: cout<< sizeof (NotEmptyNonVirt) << std::endl;
  18. qaytish EXIT_SUCCESS;
* Ushbu manba kodi Source Code Highlighter bilan ta'kidlangan.

Chiqish platformaga qarab farq qilishi mumkin, ammo mening holatimda (Win32, msvc2008) u quyidagicha edi:

Ushbu misoldan nimani tushunishingiz mumkin? Birinchidan, "bo'sh" sinfning o'lchami har doim noldan katta bo'ladi, chunki kompilyator ataylab unga qo'g'irchoq a'zoni kiritadi. Ekkel yozganidek, "nol o'lchamli ob'ektlar massivida indekslash jarayonini tasavvur qiling, shunda hamma narsa aniq bo'ladi" ;) Ikkinchidan, biz virtual funktsiyani qo'shganda "bo'sh bo'lmagan" NotEmptyVirt sinfining o'lchamini ko'ramiz. u, bekor qilish uchun ko'rsatgichning standart o'lchamiga oshirildi; va EmptyVirt "bo'sh" sinfida sinfni nolga teng bo'lmagan o'lchamli qilish uchun kompilyator avval qo'shgan qo'g'irchoq a'zo bo'lgan. almashtirildi ko'rsatkichga. Shu bilan birga, sinfga virtual bo'lmagan funksiyani qo'shish hajmiga ta'sir qilmaydi (maslahat uchun nullbie uchun rahmat). Jadval ko'rsatgichining nomi kompilyatorga qarab farqlanadi. Masalan, Visual Studio 2008 kompilyatori uni __vfptr deb ataydi va jadvalning o'zini "vftable" deb ataydi (ishonmaganlar tuzatuvchiga qarash mumkin:) Adabiyotlarda virtual funksiyalar jadvaliga ko'rsatgich odatda VPTR deb ataladi. , va jadvalning o'zi VTABLE deb ataladi, shuning uchun men bir xil belgiga yopishib qolaman.

Virtual funksiyalar jadvali nima va u nima uchun? Virtual funktsiyalar jadvali sinfning barcha virtual usullarining manzillarini (asosan, bu ko'rsatkichlar massivi), shuningdek, ushbu sinfning asosiy sinflarining barcha virtual usullarini saqlaydi.

Virtual funktsiyalarni o'z ichiga olgan sinflar qancha bo'lsa, bizda virtual funktsiyalar jadvallari bo'ladi - har bir sinf uchun bitta jadval. Har bir sinfning ob'ektlari o'z ichiga oladi shunchaki ko'rsatgich stol ustida, stolning o'zi emas! O'qituvchilar, shuningdek, suhbat o'tkazadiganlar ushbu mavzu bo'yicha savollar berishni yaxshi ko'radilar. (Yangi boshlanuvchilarni qiziqtiradigan qiyin savollarga misollar: "agar sinfda virtual funktsiyalar jadvali bo'lsa, sinf ob'ektining o'lchami undagi virtual funktsiyalar soniga bog'liq bo'ladi, to'g'rimi?"; "Bizda bir qator bor. bazaviy sinfga ko'rsatgichlar, ularning har biri olingan sinflardan birining ob'ektiga ishora qiladi - bizda nechta virtual funktsiyalar jadvali bo'ladi?

Shunday qilib, har bir sinf uchun biz virtual funktsiyalar jadvalini tuzamiz. Asosiy sinfning har bir virtual funksiyasiga ketma-ket indeks (funksiyalarni e'lon qilish tartibida) beriladi, u keyinchalik VTABLE jadvalidagi funktsiya tanasining manzilini aniqlash uchun ishlatiladi. Asosiy sinfni meros qilib olgan holda, olingan sinf asosiy sinfning virtual funktsiyalari manzillari jadvalini "qabul qiladi". Agar olingan sinfdagi biron bir virtual usul bekor qilingan bo'lsa, u holda ushbu sinfning virtual funktsiyalari jadvalida mos keladigan usul tanasining manzili shunchaki yangisi bilan almashtiriladi. Olingan sinfga yangi VTABLE virtual usullarini qo'shganda hosila sinf kengaytirildi va asosiy sinf jadvali tabiiy ravishda avvalgidek qoladi. Shuning uchun, asosiy sinfga ko'rsatgich orqali, asosiy sinfda bo'lmagan hosila sinf usullarini deyarli chaqirib bo'lmaydi - axir, asosiy sinf ular haqida "hech narsa bilmaydi" (bularning barchasini keyinroq ko'rib chiqamiz. misol).

Sinf konstruktori endi qo'shimcha operatsiyani bajarishi kerak: VPTR ko'rsatkichini mos keladigan virtual funktsiyalar jadvalining manzili bilan ishga tushiring. Ya'ni, biz olingan sinf ob'ektini yaratganimizda, birinchi navbatda "o'z" virtual funktsiyalar jadvali manzili bilan VPTR ni ishga tushiradigan asosiy sinf konstruktori chaqiriladi, keyin esa bu qiymatning ustiga yozadigan hosila sinf konstruktori chaqiriladi.

Funksiyani asosiy sinf manzili orqali chaqirganda (o'qish - asosiy sinfga ko'rsatgich orqali), kompilyator birinchi navbatda sinfning virtual funktsiyalari jadvaliga kirish uchun VPTR ko'rsatgichidan foydalanishi va undan quyidagi manzilni olishi kerak. chaqirilgan funktsiyaning tanasi va shundan keyingina qo'ng'iroq qiling.

Yuqorida aytilganlarning barchasidan xulosa qilishimiz mumkinki, kech ulanish mexanizmi erta ulanishga nisbatan qo'shimcha CPU vaqtini talab qiladi (konstruktor tomonidan VPTR-ni ishga tushirish, qo'ng'iroq qilishda funktsiya manzilini olish).

O'ylaymanki, bir misol bilan hamma narsa aniqroq bo'ladi. Quyidagi ierarxiyani ko'rib chiqing:

Bunday holda, biz virtual funktsiyalarning ikkita jadvalini olamiz:

Baza
0
Baza ::foo()
1 Baza ::bar()
2 Baza ::baz()

Va
Meroslangan
0
Baza ::foo()
1 Meros::bar()
2 Baza ::baz()
3 Meros::qux()

Ko'rib turganingizdek, olingan sinf jadvalida ikkinchi usulning manzili mos keladigan bekor qilingan bilan almashtirildi. Proofkod:

  1. #o'z ichiga oladi
  2. #o'z ichiga oladi
  3. std::cout dan foydalanish;
  4. std::endl dan foydalanish;
  5. struktura bazasi
  6. Base() (cout<< "Base::Base()" << endl; }
  7. virtual ~Base() ( cout<< "Base::~Base()" << endl; }
  8. virtual void foo() ( cout<< "Base::foo()" << endl; }
  9. virtual void bar() ( cout<< "Base::bar()" << endl; }
  10. virtual bo'shliq baz() ( cout<< "Base::baz()" << endl; }
  11. struct Meros: ommaviy baza
  12. Inherited() ( cout<< "Inherited::Inherited()" << endl; }
  13. virtual ~ Meroslangan() ( cout<< "Inherited::~Inherited()" << endl; }
  14. virtual void bar() ( cout<< "Inherited::bar()" << endl; }
  15. virtual bo'shliq qux() ( cout<< "Inherited::qux()" << endl; }
  16. int main()
  17. Baza * pBase = yangi Meros;
  18. pBase->foo();
  19. pBase->bar();
  20. pBase->baz();
  21. //pBase->qux(); // Xato
  22. pBase-ni o'chirish;
  23. qaytish EXIT_SUCCESS;
* Ushbu manba kodi Source Code Highlighter bilan ta'kidlangan.

Dastur ishga tushganda nima bo'ladi? Birinchidan, Base tipidagi ob'ektga ko'rsatgich e'lon qilamiz, unga Inherited tipidagi yangi yaratilgan ob'ektning manzilini belgilaymiz. Bu Base konstruktorini chaqiradi, VPTRni Base sinfining VTABLE manzili bilan ishga tushiradi, keyin esa VPTR qiymatini Meroslangan sinfning VTABLE manzili bilan qayta yozadigan Meroslangan konstruktorni chaqiradi. pBase->foo() , pBase->bar() va pBase->baz() chaqirilganda kompilyator VPTR ko‘rsatkichi orqali virtual funksiyalar jadvalidan funksiya tanasining haqiqiy manzilini oladi. Bu qanday sodir bo'ladi? Muayyan ob'ekt turidan qat'i nazar, kompilyator foo() funksiyasining manzili birinchi o'rinda, bar() ikkinchi o'rinda va hokazo ekanligini biladi. (Aytganimdek, funksiya deklaratsiyasi tartibida). Shunday qilib, masalan, baz() funksiyasini chaqirish uchun u funktsiya manzilini VPTR+2 ko'rinishida oladi - virtual funksiyalar jadvalining boshidan ofset, bu manzilni saqlaydi va uni chaqiruv buyrug'iga almashtiradi. Xuddi shu sababga ko'ra, pBase->qux() ni chaqirish xatolikka olib keladi: ob'ektning haqiqiy turi Meroslangan bo'lishiga qaramay, uning manzilini Base-ga ko'rsatgichga belgilaganimizda, yuqoriga siljish sodir bo'ladi va hech qanday yo'l yo'q. Base sinfining VTABLE jadvalidagi to'rtinchi usul, shuning uchun VPTR + 3 "xorijiy" xotiraga ishora qiladi (xayriyatki, bunday kod hatto kompilyatsiya qilinmaydi).

Keling, afsonaga qaytaylik. Ko'rinib turibdiki, virtual funktsiyalarni amalga oshirishga bunday yondashuv bilan ierarxiyaning faqat bitta darajasi uchun funktsiyani virtual qilish mumkin emas.

Bundan tashqari, nima uchun virtual funktsiyalar faqat ob'ekt manzili (ko'rsatkichlar yoki havolalar orqali) orqali kirishda ishlashi aniq bo'ladi. Aytganimdek, bu qatorda
Baza * pBase = yangi Meros;
yuqoriga ko'tarilish sodir bo'ladi: Inherited* Base* ga uzatiladi, lekin har qanday holatda ham ko'rsatgich ob'ektning "boshi" manzilini xotirada saqlaydi. Agar yuqoriga ko'tarish to'g'ridan-to'g'ri ob'ektda amalga oshirilsa, u aslida asosiy sinf ob'ektining o'lchamiga "kesilgan". Shuning uchun, erta bog'lanish funktsiyalarni "ob'ekt orqali" chaqirish uchun ishlatilishi mantiqan to'g'ri - kompilyator ob'ektning haqiqiy turini allaqachon "biladi".

Aslida, hammasi shu. Fikrlaringizni kutaman. E'tiboringiz uchun rahmat.

P.S. Ushbu maqola "Tezlik kafolati" deb belgilangan
(Skor, agar siz buni o'qiyotgan bo'lsangiz, bu siz uchun;)

P.P.S. Ha, aytishni unutibman... Javaistlar endi Java-da sukut bo'yicha barcha funksiyalar virtual ekanligini baqira boshlaydi.
_________
Matn tayyorlandi

"Kech bog'lash" - bu "kuchli terish" ga o'xshash kompyuter fanlari atamasi bo'lib, u turli odamlar uchun har xil narsalarni anglatadi. Men u bilan nimani nazarda tutayotganimni tasvirlay olaman deb o'ylayman.

Avvalo, "bog'lanish" nima? Agar biz "bog'lash" atamasi nimani anglatishini bilmasak, kechikish nimani anglatishini tushuna olmaymiz.

Ta'rifga ko'ra, kompilyator - bu bir tilda yozilgan matnni oladigan va boshqa tilda kod ishlab chiqaradigan qurilma "bu xuddi shu narsani anglatadi." Misol uchun, men C# tilidagi matnni kiritish sifatida qabul qiladigan va CIL (*) ni ishlab chiqaradigan kompilyatorni ishlab chiqyapman. Kompilyator tomonidan bajariladigan barcha muhim vazifalarni uchta katta guruhga bo'lish mumkin:

  • Kiritilgan matnni tahlil qilish
  • Semantik sintaksis tahlili
  • Chiqish matnini yaratish - ushbu maqolada biz ushbu bosqichga qiziqmaymiz

Kiritilgan matnni tahlil qilish haqida hech narsa bilmaydi ma'nosi tahlil qilingan matn; parser, birinchi navbatda, bilan bog'liq leksik dastur tuzilmasi (ya'ni, sharh chegaralari, identifikatorlar, bayonotlar va boshqalar), so'ngra ushbu leksik tuzilishdan aniqlaydi. grammatik dastur tuzilishi: sinflar chegaralari, usullar, operatorlar, ifodalar va boshqalar.

Keyin semantik analizator tahlil qiluvchining natijalarini oladi va turli sintaktik elementlarning ma'nolarini bog'laydi. Masalan, siz yozganingizda:

X sinf ()
B sinf ()
D toifasi: B
{
umumiy statik bekor X() ( )
umumiy statik bekor Y() ( X(); )
}

keyin tahlilchi uchta sinf mavjudligini, ulardan birida ikkita usul mavjudligini, ikkinchi usulda metod chaqiruvining ifodasi bo'lgan bayonotni o'z ichiga oladi. Semantik analizator X() ifodasida X ekanligini aniqlaydi; yuqorida e'lon qilingan X turiga emas, balki D.X() usuliga ishora qiladi. Bu so'zning keng ma'nosida "bog'lash" ga misoldir: bog'lash - bu usul nomini o'z ichiga olgan sintaktik elementning dasturning mantiqiy qismi bilan bog'lanishi.

"Erta" yoki "kech" bog'lash haqida gap ketganda, u har doim usulni chaqirish uchun nomni belgilash haqida bo'ladi. Biroq, mening nuqtai nazarimdan, bu ta'rif juda qattiq. Men kompilyatorning semantik analizatori D sinfining B sinfidan meros bo'lishini va "B" nomi sinf nomi bilan bog'liqligini aniqlash jarayonini tasvirlash uchun "bog'lash" atamasidan foydalanaman.

Bundan tashqari, men boshqa tahlil turlarini tavsiflash uchun "bog'lash" atamasidan foydalanaman. Agar sizning dasturingizda 1 * 2 + 1.0 ifodasi mavjud bo'lsa, men aytishim mumkinki, "+" operatori ikkita suzuvchi nuqtani qabul qiladigan, ularni qo'shadigan va uchinchi raqamni qaytaradigan o'rnatilgan operator bilan bog'langan. Odamlar odatda "+" nomini ma'lum bir usul bilan bog'lashni o'ylamaydilar, lekin men buni hali ham "bog'lash" deb o'ylayman.

Buni qat'iyroq qilib aytganda, men "bog'lash" atamasidan turlarning nomini bevosita ishlatmaydigan iboralar bilan bog'lanishni topish uchun foydalanishim mumkin. Norasmiy ravishda, yuqoridagi misolda 1 * 2 ifodasi int turi bilan "bog'langan", garchi u aniq turni nomlamaydi. Sintaktik ifoda to'g'ridan-to'g'ri mos keladigan nomni ishlatmasa ham, ushbu semantik element bilan qat'iy bog'liq.

Shunday qilib, umuman olganda, men aytmoqchimanki, "bog'lash" bu sintaksis daraxtining ba'zi bir qismining dasturning qandaydir mantiqiy elementi bilan bog'lanishidir. (**)

Keyin "erta" va "kech" bog'lash o'rtasidagi farq nima? Odamlar ko'pincha bu tushunchalar haqida go'yo bir-birini istisno qiladigan tanlovlar kabi gapirishadi: erta bog'lash yoki kech bog'lash. Tez orada ko'rib turganimizdek, bunday emas; ba'zi bog'lanish turlari butunlay erta, ba'zilari qisman erta va qisman kech, ba'zilari esa, albatta, butunlay kech. Ammo bunga kirishdan oldin, keling, ko'rib chiqaylik nimaga nisbatan Bog'lanish ertami yoki kechmi?

Odatda, biz "erta bog'lash" haqida gapirganda, biz "bog'lanish kompilyator tomonidan amalga oshiriladi va bog'lanish natijasi yaratilgan kodga pishiriladi" degan ma'noni anglatadi; agar bog'lanish muvaffaqiyatsiz bo'lsa, dastur ishlamaydi, chunki kompilyator kod yaratish bosqichiga o'tolmaydi. "Kechikkan bog'lash" deganda biz "bog'lanishning bir qismi ish vaqtida amalga oshiriladi" degan ma'noni bildiramiz va shuning uchun bog'lash xatolar faqat ish vaqtida paydo bo'ladi. Erta va kech bog'lanish ba'zan "statik" va "dinamik" bog'lanish deb ataladi; statik bog'lanish kompilyatorga ma'lum bo'lgan "statik" ma'lumotlar asosida amalga oshiriladi va dinamik bog'lanish faqat ish vaqtida ma'lum bo'lgan "dinamik" ma'lumotlar asosida amalga oshiriladi.

Ushbu turdagi bog'lanishning qaysi biri yaxshiroq? Shubhasiz, hech bir variant boshqasidan yaxshiroq emas; agar variantlardan biri har doim ikkinchisidan ustun bo'lsa, biz hozir hech narsani muhokama qilmagan bo'lardik. Erta ulanishning afzalligi shundaki, biz hech qanday ish vaqti xatosi yo'qligiga ishonch hosil qilishimiz mumkin; ahvolga tushib qolgani - kechikishning moslashuvchanligi yo'qligi. Erta majburiy to'g'ri qaror qabul qilish uchun zarur bo'lgan barcha ma'lumotlar dastur bajarilishidan oldin ma'lum bo'lishini nazarda tutadi; lekin ba'zida bu ma'lumot ijro etilgunga qadar mavjud emas.

Men allaqachon bog'lanish ertadan kechgacha spektrni tashkil qiladi, deb aytdim. Keling, qanday qilib erta bog'lanishdan kechgacha o'tishimiz mumkinligini ko'rsatish uchun ba'zi C# misollarini ko'rib chiqaylik.

Biz statik usulni chaqirish misoli bilan boshladik X. Bu tahlil albatta erta. Y usuli chaqirilganda D.X usuli chaqirilishiga shubha yo'q. Ushbu tahlilning hech bir qismi ish vaqtigacha qoldirilmaydi, shuning uchun bu qo'ng'iroq albatta muvaffaqiyatli bo'ladi.

Endi quyidagi misolni ko'rib chiqamiz:

B sinf
{
umumiy bekor M (juft x) ()
umumiy bekor M(int x) ()
}
C sinf
{
umumiy statik bekor X(B b, int d) ( b.M(d); )
}

Endi bizda kamroq ma'lumot bor. Biz juda ko'p erta bog'lash qilamiz; bilamizki, b o'zgaruvchisi B tipiga ega va B.M(int) usuli chaqiriladi. Ammo, oldingi misoldan farqli o'laroq, bizda qo'ng'iroq muvaffaqiyatli bo'lishiga kompilyator kafolat bermaydi, chunki b o'zgaruvchisi null bo'lishi mumkin. Aslida, biz qo'ng'iroqni qabul qiluvchining haqiqiy yoki yo'qligini tahlil qilishni ish vaqtiga qoldiramiz. Ko'pchilik bu qarorni "bog'lash" deb hisoblamaydi, chunki biz buni qilmaymiz sintaksisni dastur elementi bilan bog'lash. Keling, B sinfini o'zgartirib, biroz keyinroq C usuli ichida qo'ng'iroq qilaylik:

B sinf
{
ommaviy virtual bo'shliq M (juft x) ()
ommaviy virtual bo'shliq M(int x) ()
}

Endi biz kompilyatsiya vaqtida tahlilning bir qismini qilamiz; B.M(int) virtual usuli chaqirilishini bilamiz. Biz bilamizki, usul chaqiruvi bunday usul mavjudligi ma'nosida muvaffaqiyatli bo'ladi. Lekin biz ish vaqtida qaysi usul chaqirilishini bilmaymiz! Bu avlodda bekor qilingan usul bo'lishi mumkin; dasturning boshqa qismida aniqlangan butunlay boshqacha kod chaqirilishi mumkin. Virtual usul jo'natish kech bog'lash shaklidir; b.M(d) sintaktik konstruksiya bilan qaysi usul bog‘langanligi to‘g‘risida qaror qisman tuzuvchi tomonidan, qisman esa ish vaqtida qabul qilinadi.

Bu misol haqida nima deyish mumkin?

C sinf
{
umumiy statik boʻshliq X(B b, dinamik d) ( b.M(d); )
}

Endi bog'lash deyarli butunlay ish vaqtiga qoldirildi. Bunday holda, kompilyator Dynamic Language Runtim dasturiga statik tahlil natijasida b o'zgaruvchining statik turi B sinfi va chaqirilayotgan usul M deb nomlanishi aniqlanganligini, ammo usul ta'rifi uchun haqiqiy haddan tashqari yuklanish ruxsati B.M ekanligini bildiruvchi kod ishlab chiqaradi. (int) yoki B.M(double) (yoki agar d, masalan, string turi bo'lmasa) ushbu ma'lumotlarga asoslanib ish vaqtida bajariladi. (***)

C sinf
{
umumiy statik boʻshliq X(dinamik b, dinamik d) ( b.M(d); )
}

Endi, kompilyatsiya vaqtida, aniqlangan narsa, M deb nomlangan usulning qaysidir turiga chaqirilishi, bu amalda eng so'nggi bog'lanishdir, lekin aslida biz bundan ham uzoqroqqa borishimiz mumkin:

C sinf
{
umumiy statik bekor X(ob'ekt b, ob'ekt d, m string, BindingFlags f)
{
b.GetType().GetMethod(m, f).Invoke(b, d);
}
}

Barcha tahlillar endi kech bog'lash vaqtida amalga oshiriladi; biz ham bilmaymiz nomi nima biz chaqirilayotgan usulga bog'laymiz. Biz bilishimiz mumkin bo'lgan yagona narsa shundaki, X ning muallifi berilgan b ob'ektning nomi f ga o'tkazilgan bayroqlarga mos keladigan m ni ko'rsatadigan va d ga uzatilgan argumentlarni qabul qiladigan usulga ega bo'lishini kutadi. Bunday holda, biz kompilyatsiya vaqtida hech narsa qila olmaymiz. (****)

(*) Albatta, natija ikkilik formatda kodlangan va odam o'qiy oladigan CIL formatida emas.

(**) Siz so'rashingiz mumkin: "bog'lanish" va "semantik tahlil" sinonimi; Albatta, semantik tahlil sintaktik elementlarning ma’nolari bilan bog‘lanishidan boshqa narsa emas! Bog'lash kompilyatorning semantik tahlil bosqichining katta qismidir, ammo metod tanasi to'liq "to'plangan" dan keyin amalga oshirilishi kerak bo'lgan boshqa ko'plab tahlil shakllari mavjud. Masalan, aniq topshiriqning tahlilini hech qanday tarzda "bog'lash" deb atash mumkin emas; bu sintaktik elementlarning muayyan dastur elementlari bilan assotsiatsiyasi emas. Aksincha, bu tahlil leksikni bog'laydi joylar"Mahalliy o'zgaruvchi blah bu blokning boshida aniq belgilanmagan" kabi dastur elementlari haqidagi faktlar bilan. Xuddi shunday, arifmetik iboralarni optimallashtirish semantik tahlilning bir shakli bo'lib, "bog'lash" bilan aniq bog'liq emas.

(***) Kompilyator hali ham katta miqdordagi statik tahlilni amalga oshirishi mumkin. Faraz qilaylik, B klassi M nomli metodlarga ega bo'lmagan muhrlangan sinfdir. Dinamik argumentlar bo'lsa ham, biz kompilyatsiya vaqtida M usuliga ulanish muvaffaqiyatsiz bo'lishini bilamiz va buni kompilyatsiya vaqtida aytishimiz mumkin. Va kompilyator aslida bunday tahlilni amalga oshiradi; va boshqa suhbat uchun qanchalik yaxshi mavzu.

(****) Qaysidir ma'noda, bu misol mening bog'lash haqidagi ta'rifimga yaxshi qarshi misoldir; biz hatto ulanmaymiz sintaktik elementlar usul bilan; satr tarkibini metod bilan bog'laymiz.

VIRTUAL FUNKSIYALAR________________________________________________________________ 1

Erta va kech bog'lanish. Dinamik polimorfizm ___________________________________ 1

Virtual funksiyalar______________________________________________________________________ 1 Virtual destruktorlar _________________________________________________________________ 4 Abstrakt sinflar va sof virtual funksiyalar________________________________________________________ 5

VIRTUAL FUNKSIYALAR

Erta va kech bog'lanish. Dinamik polimorfizm

C++ polimorfizmni ikki yo'l bilan qo'llab-quvvatlaydi.

Birinchidan, u kompilyatsiya paytida funktsiya va operatorni ortiqcha yuklash orqali qo'llab-quvvatlanadi. Ushbu turdagi polimorfizm deyiladi statik polimorfizm, chunki u ijro etilishidan oldin ham amalga oshiriladi

dastur, tomonidan funktsiya identifikatorlarini jismoniy manzillar bilan erta bog'lash kompilyatsiya va bog'lash bosqichida.

Ikkinchidan, u virtual funktsiyalar orqali dasturni bajarishda qo'llab-quvvatlanadi. Dastur kodida virtual funktsiya chaqiruviga duch kelgan kompilyator (aniqrog'i, bog'lovchi) funktsiya identifikatorini uning manzili bilan assotsiatsiyasini bajarish bosqichiga qadar qoldirib, faqat shu chaqiruvni belgilaydi. Bu jarayon deyiladi kech bog'lash.

Virtual funktsiya chaqirilishi (va bajaradigan harakatlari) u chaqiriladigan ob'ekt turiga bog'liq bo'lgan funksiyadir. Ob'ekt dasturni bajarish vaqtida qaysi funktsiyani chaqirish kerakligini aniqlaydi. Ushbu turdagi polimorfizm deyiladi dinamik polimorfizm.

asos dinamik polimorfizm bu C++ tomonidan taqdim etilgan asosiy sinfga koʻrsatgichni belgilash qobiliyati boʻlib, u aslida nafaqat ushbu sinf obʼyektiga, balki olingan sinfning istalgan obyektiga ham ishora qiladi. Bu qobiliyat meros orqali keladi, chunki olingan sinf ob'ekti har doim asosiy sinf ob'ekti hisoblanadi. Kompilyatsiya vaqtida, asosiy sinf ob'ektiga ko'rsatgich berilgan holda, foydalanuvchi qaysi sinf ob'ektini yaratmoqchi ekanligi hali ma'lum emas. Bunday ko'rsatkich o'z ob'ekti bilan faqat dasturni bajarish paytida, ya'ni dinamik ravishda bog'lanadi. Kamida bitta virtual funktsiyani o'z ichiga olgan sinfga polimorfik deyiladi.

Har bir polimorfik ma'lumotlar turi uchun kompilyator virtual funktsiyalar jadvalini yaratadi va ushbu sinfning har bir ob'ektiga ushbu jadvalga yashirin ko'rsatgichni joylashtiradi. U tegishli ob'ektning virtual funktsiyalari manzillarini o'z ichiga oladi. Virtual funktsiyalar jadvaliga ko'rsatgichning nomi va jadval nomi ma'lum bir kompilyatorda amalga oshirilishiga bog'liq. Masalan, Visual C++ 6.0 da bu ko'rsatgich vfptr, jadval esa vftable (inglizcha Virtual Function Table dan) deb nomlanadi. Kompilyator virtual funktsiyalar jadvaliga ko'rsatgichni ishga tushiradigan polimorf sinf konstruktorining boshiga avtomatik ravishda kod qismini joylashtiradi. Agar virtual funktsiya chaqirilsa, kompilyator tomonidan yaratilgan kod virtual funktsiya jadvaliga ko'rsatgich topadi, so'ngra ushbu jadvalni qidiradi va undan mos keladigan funktsiyaning manzilini oladi. Shundan so'ng, belgilangan manzilga o'tish amalga oshiriladi va funktsiya chaqiriladi.

Eslatib o'tamiz, olingan sinf ob'ektini yaratishda birinchi navbatda uning asosiy sinfining konstruktori chaqiriladi. Ushbu bosqichda virtual funktsiyalar jadvali, shuningdek, unga ko'rsatgich yaratiladi. Olingan sinfning konstruktorini chaqirgandan so'ng, virtual funktsiya jadvali ko'rsatkichi ushbu sinf ob'ekti uchun mavjud bo'lgan bekor qilingan virtual funktsiyaga (agar mavjud bo'lsa) murojaat qilish uchun o'rnatiladi.

Shu munosabat bilan, siz kechikishdan foydalanish imkoniyati uchun to'lashingiz kerak bo'lgan narxdan xabardor bo'lishingiz kerak.

Virtual funktsiyalarga ega ob'ektlar virtual funktsiyalar jadvalini ham qo'llab-quvvatlashi kerakligi sababli, ulardan foydalanish har doim xotira xarajatlarining biroz oshishiga va dastur ishlashining pasayishiga olib keladi. Agar siz boshqa sinflar uchun asosiy sinf sifatida foydalanmoqchi bo'lmagan kichik sinf bilan ishlayotgan bo'lsangiz, u holda virtual funktsiyalardan foydalanishning ma'nosi yo'q.

Virtual funktsiyalar

Chaqiruvchi interfeysi (ya’ni prototipi) ma’lum bo‘lgan, lekin amalga oshirilishini umumiy ko‘rsatib bo‘lmaydigan va faqat muayyan holatlar uchun belgilanishi mumkin bo‘lgan funksiyalar virtual deb ataladi (bu atama hosila sinfda funksiyani bekor qilish mumkinligini bildiradi).

Virtual funksiyalar - qo'ng'iroq qilish uchun qanday ifoda ishlatilishidan qat'i nazar, ob'ektdagi to'g'ri funksiya chaqirilishini ta'minlaydigan funktsiyalar.

Aytaylik, asosiy sinf virtual deb e'lon qilingan funktsiyani o'z ichiga oladi va olingan sinf xuddi shu funktsiyani belgilaydi. Bunday holda, hosila sinfdan funktsiya, hatto ko'rsatgich yoki asosiy sinfga havola yordamida chaqirilgan bo'lsa ham, olingan sinf ob'ektlarida chaqiriladi. Misol:

Koord sinfi

Asosiy koordinatalar klassi

// asosiy koordinatalar klassi

himoyalangan:

// himoyalangan sinf a'zolari

juft x, y;

// koordinatalar

ommaviy:

// ommaviy sinf a'zolari

Coord() ( x = 0 ; y = 0 ; )

// asosiy sinf konstruktori

void Input();

// virtual bo'lmagan funksiyani e'lon qiladi

virtual bekor Chop etish();

// virtual funktsiyani e'lon qiladi

void Coord::Input()

// klaviaturadan koordinatalarni kiritish imkonini beradi

cout<<"\tx=";

// klaviaturadan x qiymatini kiritadi

cout<<"\ty=";

// klaviaturadan y qiymatini kiritadi

void Coord::Print()

// ekranda koordinata qiymatlarini ko'rsatadi

cout<<"\tx="<

Olingan nuqta sinfi

sinf nuqta: publicCoord

// koordinatalar sinfining merosxo'ri

belgi nomi;

// nuqta nomi

ommaviy:

// ommaviy sinf a'zolari

Nuqta (ch ar N) : Koord () (ism = N ; )

// asosiy sinf konstruktorini chaqiradi

void Input();

void Print();

void Dot::Input()

// nuqta koordinatalarini klaviaturadan kiritish imkonini beradi

char S = "Nuqtaning koordinatalarini kiriting";

CharToOem(S, S);

cout<

Koord::Input();

void Dot::Print()

// nuqtaning koordinata qiymatlarini ekranda aks ettiradi

char S = "Nuqta koordinatalari";

CharToOem(S, S);

// satr belgilarini kirill alifbosiga o'zgartiradi

cout<

// nuqta sarlavhasi va nomini ko'rsatadi

Kord::Print();

// asosiy sinf funksiyasini chaqiradi

sinf Vec: publicCoord

Olingan vektor sinfi

// koordinatalar sinfining merosxo'ri

belgi nomi [3];

// vektor nomi

ommaviy:

// ommaviy sinf a'zolari

Vec (char * pName): Koord () ( strncpy (nom , pName , 3) ​​; nom [ 2 ] = "\ 0" ; )

void Input();

// virtual bo'lmagan funksiyani bekor qiladi

void Print();

// virtual funksiyani bekor qiladi

void Vec::Input()

// vektor proyeksiyalarini klaviaturadan kiritish imkonini beradi

9-ma’ruza Virtual funksiyalar 3

char S = "Vektorning proyeksiyalarini kiriting";// so'rov satrini e'lon qiladi va ishga tushiradi

CharToOem(S, S);

// satr belgilarini kirill alifbosiga o'zgartiradi

cout<

// so'rov va vektor nomini ko'rsatadi

Koord::Input();

// asosiy sinf funksiyasini chaqiradi

void Vec::Print()

// ekranda vektor proyeksiyalarining qiymatlarini ko'rsatadi

char S = "Vektor proyeksiyalari";

// sarlavha satrini e'lon qiladi va ishga tushiradi

CharToOem(S, S);

// satr belgilarini kirill alifbosiga o'zgartiradi

cout<

// vektorning sarlavhasi va nomini ko'rsatadi

Kord::Print();

// asosiy sinf funksiyasini chaqiradi

Yuqoridagi misolda Coord tayanch sinfi va ikkita hosilaviy sinf Dot va Vec e'lon qilingan. Olingan sinflardagi Print() funksiyasi virtualdir, chunki u Coord asosiy sinfida virtual deb e'lon qilingan. Dot va Vec olingan sinflardagi Print() funksiyasi asosiy sinf funksiyasini bekor qiladi. Agar olingan sinf Print() funksiyasining bekor qilingan bajarilishini ta'minlamasa, asosiy sinfdan standart dastur qo'llaniladi.

Input() funktsiyasi Coord asosiy sinfida virtual emas deb e'lon qilinadi va Dot va Vec hosila sinflarida bekor qilinadi.

void main()

Coord* pC = new Coord();

// koordinatalar uchun ko'rsatgichni e'lon qiladi va xotirani ajratadi

Dot* pD = new Dot("D");

// nuqtaga ko'rsatgichni e'lon qiladi va xotirani ajratadi

Vec* pV = new Vec("V");

// vektorga ko'rsatgichni e'lon qiladi va xotirani ajratadi

pC->Kirish ();

pC->Chop etish ();

// virtual funktsiyani chaqiradi Coord::Print()

// koordinatalar uchun ko'rsatgich nuqta tipidagi ob'ektning manzilini oladi

pC->Kirish ();

// virtual bo'lmagan funktsiyani chaqiradi Coord::Input()

pC->Chop etish ();

// virtual funksiyani chaqiradi Dot::Print()

// koordinatalarga ko'rsatgich vektor tipidagi ob'ekt manzilini oladi

pC->Kirish ();

// virtual bo'lmagan funktsiyani chaqiradi Coord::Input()

pC->Chop etish ();

// virtual Vec::Print() funksiyasini chaqiradi

Yuqoridagi misolda, koordinata ko'rsatkichi pC navbatma-navbat koordinata ob'ektlari manzili, nuqta va vektor qiymatlarini oladi. Kompyuter ko'rsatkichi turi o'zgarmasa-da, uning qiymatiga qarab turli xil virtual funktsiyalarni chaqiradi.

Haqiqatan ham olingan sinf ob'ektiga ishora qiluvchi asosiy sinf ko'rsatkichidan foydalansangiz, asosiy sinfning virtual bo'lmagan funktsiyasi chaqiriladi.

Shuni ta'kidlash kerakki, har xil turdagi operandlarni (Coord* va Dot*) konvertatsiya qilmasdan ishlatadigan pC = pD tayinlash operatsiyasi faqat chap tomondagi asosiy sinf ko'rsatkichi uchun mumkin. Teskari tayinlash operatsiyasi pD = pC noto'g'ri va sintaksis xatosini keltirib chiqaradi.

Ishga tushganda, dastur ko'rsatadi:

D nuqtasi koordinatalari:

V vektorning proyeksiyalari:

Ko'rsatkichlar va havolalar yordamida funktsiyani chaqirishda quyidagi qoidalar qo'llaniladi:

virtual funktsiyaga qo'ng'iroq manzili ko'rsatgich yoki ma'lumotnoma tomonidan saqlanadigan ob'ekt turiga qarab hal qilinadi;

virtual bo'lmagan funksiyaga qo'ng'iroq ko'rsatgich yoki havola turiga qarab hal qilinadi.

Virtual funktsiyalar faqat ma'lum bir sinfga tegishli ob'ektlarda chaqiriladi. Shunung uchun

Global yoki statik funktsiyani virtual deb e'lon qila olmaysiz. Virtual kalit so'z mumkin

---.NET Assambleyalari --- Kech bog'lash

Kechiktirilgan bog'lash- kompilyatsiya vaqtida mavjudligini qattiq kodlamasdan, ma'lum bir turni yaratish va uning a'zolarini ish vaqtida chaqirish imkonini beruvchi texnologiya. Ba'zi tashqi yig'ilishdagi turga kech ulanishni talab qiladigan dasturni yaratishda ushbu yig'ilishga havola qo'shish uchun hech qanday sabab yo'q va shuning uchun u chaqiruvchi kod manifestida to'g'ridan-to'g'ri ko'rsatilmagan.

Bir qarashda, kech bog'lashning afzalliklarini ko'rish oson emas. Haqiqatan ham, agar ob'ektni erta bog'lashni amalga oshirish mumkin bo'lsa (masalan, yig'ish havolasini qo'shish va new kalit so'zidan foydalanib turni joylashtirish), siz buni albatta qilishingiz kerak. Eng jiddiy sabablardan biri shundaki, erta bog'lash xatolarni ishlash vaqtida emas, balki kompilyatsiya vaqtida aniqlash imkonini beradi. Biroq, kech bog'lash siz yaratgan har qanday kengaytiriladigan ilovada ham muhim rol o'ynaydi.

System.Activator sinfi

Sinf tizimi. Aktivator (mscorlib.dll assambleyasida belgilangan) .NETda kechikish jarayonida asosiy rol o'ynaydi. Mavjud misolda, hozircha qiziqish uyg'otadigan yagona narsa Activator.CreateInstance() usuli, bu sizga kech bog'langan turni yaratishga imkon beradi. Ushbu usul bir nechta ortiqcha yuklarga ega va shuning uchun juda yuqori moslashuvchanlikni ta'minlaydi. O'zining eng oddiy versiyasida CreateInstance() tezkor xotiraga ajratilishi kerak bo'lgan ob'ektni tavsiflovchi tegishli Type ob'ektini oladi.

Bu nimani anglatishini bilish uchun keling, Console Application tipidagi yangi loyiha yaratamiz, unga using kalit so‘zi yordamida System.I0 va System.Reflection nom bo‘shliqlarini import qilamiz va keyin Quyida ko‘rsatilgandek Program sinfini o‘zgartiramiz:

Tizimdan foydalanish; System.Reflection yordamida; System.IO dan foydalanish; nom maydoni ConsoleApplication1 ( sinf Dasturi ( statik void Main() ( Assembly ass = null; try ( ass = Assembly.Load("fontinfo"); ) catch (FileNotFoundException ex) ( Console.WriteLine(ex.Message); ) if (ass) != null) CreateBinding(ass); color1);

Ushbu ilovani ishga tushirishdan oldin, fontinfo.dll majmuasini Windows Explorer yordamida ushbu yangi ilova katalogidagi bin\Debug pastki katalogiga qo'lda nusxalashingiz kerak. Gap shundaki, bu erda Assembly.Load() usuli chaqiriladi, ya'ni CLR faqat mijoz papkasini tekshiradi (agar xohlasangiz, Assembly.LoadFrom() usulidan foydalanishingiz va montajga to'liq yo'lni belgilashingiz mumkin, lekin bu holda bu holatda bunga ehtiyoj yo'q).

Bu nima ekanligi bilan qisqacha tanishib chiqdik. Aslida, bu subklasslardagi superklass usullarini bekor qiladi. Ammo, ehtimol, buning barcha kuchi va go'zalligi hali to'liq aniq emas. Va bularning barchasi nima uchun kerakligi to'liq tushunilmasligi mumkin. Endi buni chuqurroq tushunishga harakat qilaylik. Chuqur meditatsiyaga tayyorlaning. Ommmmm…. Xo'sh, ketaylik! :)

Keling, raqamlar bilan eskirgan misolni olaylik. Keling, janr klassikasidan chetga chiqmaylik :)

Shunday qilib, bizning umumiy supersinfimiz Shakl sinfi bo'ladi va uning davomchilari shoh, shahzoda, qirol, shahzoda, doira, kvadrat, uchburchak bo'ladi. Lekin biz xakeriy misoldan bir oz uzoqroqqa boramiz :) va yana bir nechta merosxo'rlarni yaratamiz. Oval Circle vorisi bo'ladi va Rect Square vorisi bo'ladi.

Diagrammada hamma narsani quyidagicha tasvirlash mumkin:

Har bir sinfdagi drow() usullari bekor qilinadi va erase() usuli shunchaki Shapedan meros qilib olinadi. Endi faqat koddagi bu go'zallik bilan o'ynash qoladi :)

Kodimiz juda chiroyli chiqdi :) Harfma-harf :) va uning chiqishi bir xil :)

Endi kodni batafsil ko'rib chiqaylik. Bizda 6 o'lchamli Shape sinflarining bir o'lchovli shakllar massivi mavjud. Biz massivning birinchi elementiga Shape ob'ektiga havolani tayinladik (yangi Shape() yaratiladi). Ammo keyin siz allaqachon ko'rgan va tushunishingiz kerak bo'lgan sehr boshlanadi. Bu yuqoriga konvertatsiya deb ataladi. Yuqori sinf havolasi pastki sinflar ob'ektlariga ishora qilishi mumkinligi haqida men allaqachon gapirgan edim. Va shunga o'xshab, biz pastki sinf havolalarini shakllar massivining keyingi elementlariga tayinlaymiz. Ammo keyin polimorfizmning aqldan ozgan sehri chiqishda ishlaydi - havola superklass turiga ega bo'lsa-da, pastki sinflar usullari deyiladi.

Endi savol tug'iladi, kompilyator qaysi ob'ekt usulini chaqirish kerakligini qanday biladi?

Lekin kompilyator ham bilmaydi... :) Xo'sh, unda kim biladi?

Kim, kim? Palto kiygan ajdaho!

Yuqoridagi dasturda bu unchalik aniq bo'lmasa-da, kompilyator bilmaydi, chunki biz massiv elementlariga aniq ob'ektlarga havolalarni tayinlaymiz.

Lekin nima bo'layotganini tushunish va ravshanlik uchun men buni qildim.

Hamma narsa adolatli bo'lishi va kompilyator massiv elementlari qaysi ob'ektga murojaat qilishini aniq bilmasligini aniq ko'rsatish uchun ushbu dasturni massivni tasodifiy to'ldirish uchun o'zgartirish mumkin.

Chapda xuddi shu dasturning o'zgartirilgan ko'chirmasiga misol, lekin dasturning chiqishidan ko'rinib turibdiki, massiv allaqachon tasodifiy to'ldirilgan:

Xuddi shu savol tug'iladi - har bir alohida holatda qaysi ob'ekt usulini chaqirish kerakligini kim biladi? Va JVM buni biladi. Ammo u qayerdan biladi? Java virtual mashinasi va Java kompilyatorining jiddiy sehri shu erda boshlanadi.

Kompilyatorning o'zi bilmaydi, lekin u JVM ga usulni chaqirish ko'rsatmalarini qanday qayta ishlashni aytib berishi mumkin.

Nima sodir bo'layotganining mohiyatini to'liq tushunish uchun majburiy tushunchani ko'rib chiqish kerak ( bog'lash).

Usul tanasiga usul chaqiruvini biriktirish bog'lash deyiladi. Agar bog'lanish dastur ishga tushishidan oldin amalga oshirilsa (agar mavjud bo'lsa, kompilyator va bog'lovchi tomonidan), u erta ulanish deb ataladi ( erta bog'lash). Protsessual tillarda majburiy tanlov yo'q edi. Kompilyatorlar C qo'ng'iroqning faqat bitta turini qo'llab-quvvatlash - erta ulanish.

Bizning dasturimizda qaysi ob'ekt usulini chaqirish kerakligini aniqlash muammosi kech bog'lanish tufayli hal qilinadi ( kech bog'lash), ya'ni ob'ekt turiga qarab dasturni bajarish jarayonida amalga oshiriladigan bog'lash. Kech bog'lanish dinamik deb ham ataladi ( dinamik bog'lanish) yoki ish vaqtini bog'lash ( ish vaqtini bog'lash).

Kech ulanishni amalga oshiradigan tillarda tegishli usulni chaqirish uchun ish vaqtida ob'ektning haqiqiy turini aniqlash mexanizmi bo'lishi kerak. Boshqacha qilib aytganda, kompilyator ob'ekt turini bilmaydi, lekin usulni chaqirish mexanizmi uni aniqlaydi va mos keladigan usul tanasini chaqiradi. Kech bog'lanish mexanizmi o'ziga xos tilga bog'liq, ammo uni amalga oshirish uchun ob'ektlarga ba'zi qo'shimcha ma'lumotlar kiritilishi kerak, deb taxmin qilish qiyin emas. Endi biz bu ma'lumot nima ekanligini aniqlashga harakat qilamiz.

Oxirgi postda biz bu masalaga qisqacha to'xtalib o'tdik. Endi chuqurroq tushunishga harakat qilaylik.

Barcha Java usullari, agar usul deb e'lon qilinmagan bo'lsa, kech bog'lashdan foydalanadi xususiy . Qo'ng'iroq qiling xususiy usul bayt-kod ko'rsatmalariga kompilyatsiya qilinadi invokespecial, bu ma'lum bir sinfdan usulni amalga oshirishni chaqiradi, kompilyatsiya vaqtida aniqlanadi. Boshqa kirish darajasiga ega usulni chaqirish uchun kompilyatsiya qilinadi chaqiruv virtual, bajarilish vaqtida ob'ekt turiga mos yozuvlar bo'yicha allaqachon qaraydi. Yakuniy xususiy bo'lmagan usullar ham orqali deyiladi chaqiruv virtual.

Invokespesial bayt-kod ko'rsatmalariga quyidagilar kiritilgan:

  • Boshlash chaqiruvi ( ) ob'ektni yaratishda
  • Qo'ng'iroq qiling xususiy usuli
  • Kalit so'z yordamida usulni chaqirish super

Albatta, qo'ng'iroq qilish usullari uchun yana bir qancha bayt-kod ko'rsatmalari mavjud: chaqiruvchi dinamik , chaqirish interfeysi Va invokestatik. Ammo ularning nomlari ulardan foydalanishni ko'rsatsa ham, biz ularni hozircha muhokama qilmaymiz. Agar kimdir chindan ham hohlasa, har bir haqiqatga ishongan dasturchiga dushman bo'lgan burjua tilida o'qishi mumkin :) Bu foydali o'qish, lekin hozir nima haqida gaplashayotganimizni tushunish uchun bu erda allaqachon yozganlarim kifoya. . Siz o'z ona tilingizda ham o'qishingiz mumkin.

Shunday qilib, biz amaliyotga o'tishimiz kerak. Keling, ushbu postdan dasturni quyidagicha o'zgartiramiz:

ta'kidladim xususiy Va final modifikatorlarga e'tibor berishingiz uchun va keyin kompilyator ular uchun qanday bayt kodini yaratadi. Hozirda dasturimizning natijasi quyidagicha:

Sizning e'tiboringizni ildiz havolasi Root tipidagi, lekin Branch tipidagi ob'ektga ishora qilishiga qarataman. Va men bir necha marta yozganimdek, oddiy usullar havola ko'rsatadigan ob'ekt versiyasiga ko'ra chaqiriladi. Aynan shu xususiyat orqali polimorfizm amalga oshiriladi.

Ammo bizning holatlarimizda, shunga qaramay, birinchi buyruq konsolda filialni emas, balki ildizni ko'rsatdi.

Endi buyruq yordamida ushbu dasturning qopqog'ini ko'rib chiqamiz: javap -c -p -v Root.class

Bu buyruq juda uzoq natija hosil qiladi, lekin bizga faqat ushbu qism kerak:

Chiqishdan ko'rinib turibdiki, root.prt() buyrug'i kabi qo'ng'iroqqa aylantirildi invokespecial, va filial.prt() buyrug'i chaqiruv virtual.

Shunday qilib, biz butun bu harakatning sehrini ochib berdik. Umid qilamanki, taqdimot sizga yoqdi :) va endi siz Java-da polimorfik usullar qanday ishlashini biroz ko'proq tushunasiz.

Boshlash