تماس درباره   صفحه اصلی
  زبان ++C > چندريختی  
 
 

چندريختی


چندريختی در C++ توسط توابع مجازی پياده سازی می شود. تابع مجازی تابع عضوی است که انتظار می رود در کلاس های مشتق شده دوباره تعريف شود. درک چندريختی بدون استفاده از توارث و انتزاع غيرممکن است.

تابع مجازی
اشاره گر به نوع پايه و مشتق شده
کلاس های مجرد و توابع مجازی محض
سازنده ها و مخرب ها درچندريختی


چندريختی (polymorphism) يکی از ويژگی های زبان های شیءگراست. به واسطه چندريختی توابع می توانند به شيوه های مختلف پياده سازی شوند ولی از طريق يک اسم يکسان در دسترس قرار بگيرند.

چندريختی در ++C به دو شکل پشتيبانی می شود؛ در زمان کامپايل و در زمان اجرا. سربارگذاری توابع و عملگرها نمونه هائی از چندريختی در زمان کامپايل هستند. چندريختی در زمان اجرا با تلفيق وراثت و توابع مجازی حاصل می شود.


تابع مجازی

يک تابع مجازی (virtual function) به تابعی گفته می شود که در کلاس پايه اعلان شده است و مجددا توسط کلاس مشتق شده تعريف می شود. اگر کلاس پايه شامل تابع مجازی باشد کلاس مشتق شده می تواند اين تابع مجازی را با توجه به نيازهای خود مجدد تعريف می کند.

برای ايجاد يک تابع مجازی کلمه کليدی virtual در ابتدای اعلان تابع اضافه می شود.


مثال. تابع مجازی show در کلاس پايه دوباره در کلاس مشتق شده اعلان شده است.

#include <iostream.h>
class Base {
public:
  virtual void Show() {
    cout << "Base::Show" << endl;
  }
};

class Derived : public Base {
public:
  void Show() {
    cout << "Derived::Show" << endl;
  }
  void Value(int i) {
    cout << i << endl;
  }
};

int main() {
  Derived d;
  d.Show();
}


نکته. به کلاسی که دارای يک تابع مجازی باشد کلاس پلی مورفيک (polymorphic) می گويند.
نکته. وقتی يک تابع مجازی در کلاس مشتق شده دوباره تعريف می شود نوشتن عبارت virtual در کلاس مجازی الزامی نيست.
نکته. اگر کلاس مشتق شده تابع مجازی کلاس پايه را مجددا تعريف نکند نسخه پيش فرض کلاس پايه استفاده می شود.
نکته. نمی توانيد مقدار برگشتی يک تابع مجازی را در طی ابطال تغيير دهيد. اگر نوع برگشتی تابع override با تابع مجازی متفاوت باشد نوع پارامترها هم بايد تفاوت داشته باشند.


اشاره گر به نوع پايه و مشتق شده

در مثال بالا تابع show کلاس مشتق شده تابع show کلاس پايه را باطل (override) می کند. بنابراين وقتی شیئی از کلاس مشتق شده تعريف می شود کامپايلر تابع show کلاس مشتق شده را فراخوانی می کند. برای دسترسی به تابع کلاس پايه از اشاره گرها می توان استفاده کرد. با اشاره گری به کلاس مشتق شده يا به کلاس پايه می توان تابع مجازی و نسخه مشتق شده آن را اجرا کرد.

اشاره گر به کلاس پايه و کلاس های مشتق شده با هم در ارتباطند. اشاره گر به کلاس پايه می تواند برای اشاره به شیئی از هر کلاسی که از آن مشتق شده است بکار گرفته شود.


مثال. فرض کنيد کلاس پايه Base و کلاس مشتق شده از آن Derived نامگذاری شده اند. هر اشاره گری به کلاس Base می تواند به Derived هم اشاره کند.

Base *p;
Base b;
Derived d;
p= &b;   //Points to object of Base
p= &d;   //Points to object of Derived

اشاره گر پايه p می تواند به کليه اعضای Derived که از Base به ارث گرفته است دسترسی پيدا کند اما نمی تواند به اعضای مشخص شده کلاس مشتق شده دسترسی داشته باشد.


گرفتن آدرس يک شیء و کار با آن به صورت آدرس نوع پايه را upcasting می نامند.


مثال. در برنامه زير اشاره گر پايه p به اعضای ارث گرفته شده کلاس مشتق شده دسترسی دارد اما نمی تواند به توابع get و showDerived کلاس مشتق شده دسترسی پيدا کند.

#include <iostream.h>
class Base {
  int i;
public:
  void set(int ii) { i = ii; }
  void showBase() { cout << "Base " << i << endl; }
};

class Derived : public Base {
  int j;
public:
  void get() { cin >> j; }
  void showDerived() { cout << "Derived " << j << endl; }
};

int main() {
  Base b, *p;
  Derived d, *q;
  p= &b;          //address of Base Class
  p->set(5);    //access b via pointer
  p= &d;         //address of Base Class
  p->set(6);   //access d via base pointer
  b.showBase();    //acess directly
  d.showBase();
  q=&d;         //address od Derived Class
  q->get();    //access d via pointer
  p->showBase();       //either p or q can be used here
  q->showDerived();    //only p can not be used here
}


وقتی فراخوانی تابعی توسط اشاره گر صورت می گيرد قوانين زير رعايت می شود:

• يک فراخوانی به تابع مجازی طبق نوع شیء تفکيک می شود.
• يک فراخوانی به تابع غيرمجازی طبق نوع اشاره گر تبديل می شود.


نکته. هرچند اشاره گر کلاس پايه می تواند برای اشاره به نوع مشتق شده استفاده شود ولی عکس اين موضوع مصداق ندارد.
نکته. در صورتی که بخواهيم توسط اشاره گر پايه به مولفه های يک کلاس مشتق شده دسترسی پيدا کنيم بايد نوع آنرا به نوع کلاس مشتق شده تغيير دهيد.


مثال.

((Derived *)p) ->showDerived();

مثال.مکانيسم فراخوانی تابع مجازی می تواند با بيان صريح نام تابع و عملگر :: ملغی شود.

Derived *p, d;
p=&d;
p->Base::Show();    //explicit qualification.


کلاس های مجرد و توابع مجازی محض

اغلب در طراحی نياز است کلاس پايه تنها واسطی برای کلاس های مشتق شده خودش باشد و نمی خواهيد کسی شیئی از نوع پايه ايجاد کند. اين عمل با مجرد (abstract) کردن کلاس صورت می گيرد. برای مجرد کردن کافيست کلاس پايه حداقل يک تابع مجازی محض را داشته باشد.

تابع مجازی محض (pure virtual) تابعی است که در کلاس پايه اعلان شده است و تعريفی ندارد، هر کلاس مشتق شده بايد نسخه مربوط به خود را تعريف کند. اگر کسی سعی کند شیئی از نوع کلاس مجرد ايجاد کند کامپايلر جلوی آنرا می گيرد.

بدنبال اعلان يک تابع مجازی محض =0 قرار می گيرد.

وقتی يک کلاس مجرد به ارث برده می شود کليه توابع مجازی محض بايد پياده سازی شوند يا کلاس مشتق شده هم بايد مجرد باشد. ايجاد يک تابع مجازی محض اجازه می دهد تابعی را بدون نياز به داشتن بدنه در کلاس پايه اضافه کنيد. درضمنی که کلاس های مشتق شده را الزام به تعريف آن میکند.


مثال.

#include <iostream.h>
enum note { middleC, Csharp, Cflat };

class Instrument {
public:
  virtual void play(note) = 0;
  virtual char* what() = 0;
};
class Wind : public Instrument {
public:
  void play(note){
    cout << "Wind::play" << endl;
  }
  char* what() { return "Wind"; }
  void adjust(int) {cout << "adjusted" << endl;}
};

int main() {
  Wind flute;
  cout << flute.what();
  flute.play(Cflat);
}


سازنده ها و مخرب ها درچندريختی

سازنده ها وظيفه خاص تکه تکه قرار دادن يک شیء در کنار هم را دارند. ابتدا سازنده پايه سپس سازنده های مشتق شده به ترتيب وراثت فراخوانی می شوند. مخرب ها هم بايد يک شیء را که به سلسله مراتبی از کلاس ها تعلق دارد خراب کنند. برای اينکار کامپايلر کليه مخرب ها را به ترتيب عکس فراخوانی سازنده ها اجرا می کند. به اين ترتيب می توانيد در مخرب تابع عضو کلاس پايه را درصورت نياز فراخوانی کنيد.

بخاطر داشته باشيد اين سلسله مراتب تنها در فراخوانی سازنده ها و مخرب ها اتفاق می افتد. در کليه توابع ديگر تنها همان تابع فراخوانی می شود چه مجازی باشد چه نباشد.

نمی توانيد کلمه virtual را قبل از تابع سازنده قرار دهيد اما توابع مخرب اغلب مجازی هستند.


مثال. برنامه زير تفاوت مخرب مجازی و غير مجازی را نشان می دهد.

#include <iostream.h>
class Base1 {
public:
  ~Base1() { cout << "~Base1()\n"; }
};

class Derived1 : public Base1 {
public:
  ~Derived1() { cout << "~Derived1()\n"; }
};

class Base2 {
public:
  virtual ~Base2() { cout << "~Base2()\n"; }
};

class Derived2 : public Base2 {
public:
  ~Derived2() { cout << "~Derived2()\n"; }
};

int main() {
  Base1* bp = new Derived1; // Upcast
  delete bp;
  Base2* b2p = new Derived2; // Upcast
  delete b2p;
}

مثال. تابع مخرب مجازی محض مجاز است به شرطی که بدنه ای برای آن فراهم شده باشد.

#include <iostream.h>
class AbstractBase {
public:
  virtual ~AbstractBase() = 0;
};
AbstractBase::~AbstractBase() {}
class Derived : public AbstractBase {};
// No overriding of destructor necessary?
int main()
{
  Derived d;
}


 


 


صفحه اصلی| PDF| درباره| تماس