Ana sayfa > Programlama > C++ Programlama > Sanal (virtual) fonksiyonlar

Sanal (virtual) fonksiyonlar

► Detaylı anlatım

Sanal fonksiyonlar bir ana sınıf içinde bildirimi yapıldıktan sonra türetilen sınıf içinde tekrar tanımlanır. Sanal fonksiyon bildirimi için kullanılan genel yapı aşağıda gösterilmektedir:

class ana-sınıf {
  public:
    virtual veri-türü fonk-adı(parametreler)
    {
      // kod bloğu   
    }
}

class türetilen-sınıf public: ana-sınıf {
  public:
    veri-türü fonk-adı(parametreler)
    {
      // kod bloğu   
    }
}

Sanal bir fonksiyon oluşturmak için, ana sınıf içindeki fonksiyon bildiriminden önce virtual anahtar kelimesi kullanılır. Sanal fonksiyon içeren bir sınıftan yeni bir sınıf türetildiğinde, türetilen her sınıf içinde sanal fonksiyon, virtual anahtar kelimesi kullanılmadan, yeniden tanımlanır ve fonksiyon içeriği yeniden düzenlenir.

Sanal fonksiyon ile, aynı isme sahip bir fonksiyon her sınıf için ayrı bir işlem yapacak şekilde tanımlanabilmektedir. Bu durum nesneye yönelik programlamada çok biçimlilik (Polymorphism) özelliğinin bir sonucudur.

Sanal fonksiyonlar çalışma zamanı çok biçimlilik (Runtime Polymorphism) özelliği sağlar. Bu özellikle, virtual anahtar kelimesi derleyiciye fonksiyon bağlama işlemini derleme zamanında değil çalışma zamanında yapmasını bildirir.

İşaretçi kullanarak erişim sağlamak sanal fonksiyonları sınıf içindeki diğer fonksiyonlardan farklı hale getirir. Ana sınıf türünden oluşturulan bir işaretçi ana sınıf ve ana sınıftan türetilen tüm sınıflar içindeki sanal fonksiyonlara erişim sağlayabilir.

İlk bakışta, ana sınıf içinde bildirimi yapılan sanal bir fonksiyonunun ana sınıftan türetilen bir sınıf içinde yeniden tanımlanması, fonksiyon için çoklu işlem tanımlama (overloading) işlemi ile benzerlik gösterir. Ancak, türetilen sınıf içindeki sanal fonksiyonun yeniden tanımlanmasında kullanılan bildirim yapısı ana sınıf içindeki sanal fonksiyon ile aynı olmalıdır. Fonksiyon çoklu işlem tanımlama işleminde fonksiyon parametre sayısı ve veri türlerinden birisi mutlaka farklı olmalıdır. Ayrıca, fonksiyonun geri döndürdüğü değer de farklı olabilir. Eğer türetilen sınıf içindeki sanal fonksiyon bildirim yapısı ana sınıf içindekinden farklı olursa, fonksiyonun sanal özelliği kaybolur ve fonksiyon çoklu işlem tanımlama işlemi uygulanır.

Sanal fonksiyonların ana sınıf işaretçisi ile çağrılması

Sanal fonksiyonlar genel olarak ana sınıf işaretçisi kullanılarak çağrılır. Bu yöntemde, ana sınıftan ve türetilen sınıflardan oluşturulan nesnelerin adresleri ana sınıf türünden tanımlanan işaretçiye atanarak her bir sınıfta yer alan sanal fonksiyona çağrı yapılır.

Sanal fonksiyon tanımlamayı bir örnek üzerinde incelemeye çalışalım:


#include <iostream>

using namespace std;

class sinifana {
  private:
    int priidana;

  protected:
    int proidana;

  public:
    int pubidana;
    // Sanal fonksiyon bildirimi
    virtual void deger_topla(void) {
      cout << "sinifana değişken toplamları: "<< priidana + proidana + pubidana << endl;
    }
    void deger_ata(int pid1, int pid2, int pid3)
    {
      priidana = pid1; proidana = pid2; pubidana = pid3;
      cout << "sinifana değişkenlerine değer atama: " << priidana << " " << proidana << " " << pubidana << endl;
    }
    void deger_goster()
    {
      cout << "sinifana değişken değerleri: " << priidana << " " << proidana << " " << pubidana << endl;
    }
};

class siniftur1 : public sinifana {
  private:
    int priidtur1;

  protected:
    int proidtur1;

  public:
    int pubidtur1;
    // Sanal fonksiyonun siniftur1 için yeniden tanımlanması
    void deger_topla(void) {
      cout << "siniftur1 değişken toplamları: "<< priidtur1 + proidtur1 + pubidtur1 << endl;
    }
    void deger_ata_tur1(int pid1, int pid2, int pid3)
    {
      priidtur1 = pid1; proidtur1 = pid2; pubidtur1 = pid3;
      cout << "siniftur1 değişkenlerine değer atama: " << priidtur1 << " " << proidtur1 << " " <<  pubidtur1 << endl;
    }
    void deger_goster_tur1() { cout << "siniftur1 değişken değerleri: " << priidtur1 << " " << proidtur1 << " " << pubidtur1 << endl; }
};

class siniftur2 : public sinifana {
  private:
    int priidtur2;

  protected:
    int proidtur2;

  public:
    int pubidtur2;
    // Sanal fonksiyonun siniftur2 için yeniden tanımlanması
    void deger_topla(void) {
      cout << "siniftur2 değişken toplamları: "<< priidtur2 + proidtur2 + pubidtur2 << endl;
    }
    void deger_ata_tur2(int pid1, int pid2, int pid3)
    {
      priidtur2 = pid1; proidtur2 = pid2; pubidtur2 = pid3;
      cout << "siniftur2 değişkenlerine değer atama: " << priidtur2 << " " << proidtur2 << " " <<  pubidtur2 << endl;
    }
    void deger_goster_tur2() { cout << "siniftur2 değişken değerleri: " << priidtur2 << " " << proidtur2 << " " << pubidtur2 << endl; }
};

int main(void)
{
  sinifana *pnes_ana, nes_ana;
  siniftur1 nes_tur1;
  siniftur2 nes_tur2;

  cout << "pnes_ana işaretçisi ile nes_ana değişkeninin sinifana elemanlarına erişimi:" << endl;
  cout << "---------------------------------------------------------------------------" << endl;
  pnes_ana = &nes_ana;                // sinifana türünden işaretçi sinifana nesnesinin adresini gösteriyor.
  pnes_ana->deger_ata(17, 874, 1345);
  pnes_ana->deger_goster();
  pnes_ana->deger_topla();            // sinifana sanal fonksiyonuna erişim.
  nes_ana.deger_topla();              // Yukarıdaki işlem satırı ile aynı işlemi yapar.

  cout << endl << "nes_tur1 değişkeninin siniftur1 elemanlarına erişimi:" << endl;
  cout << "-----------------------------------------------------" << endl;
  nes_tur1.deger_ata_tur1(15, 74, 384);
  nes_tur1.deger_goster_tur1();
  pnes_ana = &nes_tur1;               // sinifana türünden işaretçi siniftur1 nesnesinin adresini gösteriyor.
  pnes_ana->deger_topla();            // Burada işaretçinin siniftur1 fonksiyonuna erişmesinin nedeni fonksiyonun sanal olarak tanımlanması
  nes_tur1.deger_topla();             // Yukarıdaki işlem satırı ile aynı işlemi yapar.

  cout << endl << "nes_tur2 değişkeninin siniftur2 elemanlarına erişimi:" << endl;
  cout << "-----------------------------------------------------" << endl;
  nes_tur2.deger_ata_tur2(95, 291, 752);
  nes_tur2.deger_goster_tur2();
  pnes_ana = &nes_tur2;               // sinifana türünden işaretçi siniftur2 nesnesinin adresini gösteriyor.
  pnes_ana->deger_topla();            // Burada işaretçinin siniftur2 fonksiyonuna erişmesinin nedeni fonksiyonun sanal olarak tanımlanması
  nes_tur2.deger_topla();             // Yukarıdaki işlem satırı ile aynı işlemi yapar.

  return 0;
}

Yukarıdaki programı derleyip çalıştırdığımızda, aşağıdaki ifadeleri ekrana yazar:

pnes_ana işaretçisi ile nes_ana değişkeninin sinifana elemanlarına erişimi:
--------------------------------------------------------------------------
sinifana değişkenlerine değer atama: 17 874 1345
sinifana değişken değerleri: 17 874 1345
sinifana değişken toplamları: 2236
sinifana değişken toplamları: 2236

nes_tur1 değişkeninin siniftur1 elemanlarına erişimi:
------------------------------------------------------
siniftur1 değişkenlerine değer atama: 15 74 384
siniftur1 değişken değerleri: 15 74 384
siniftur1 değişken toplamları: 473
siniftur1 değişken toplamları: 473

nes_tur2 değişkeninin siniftur2 elemanlarına erişimi:
------------------------------------------------------
siniftur2 değişkenlerine değer atama: 95 291 752
siniftur2 değişken değerleri: 95 291 752
siniftur2 değişken toplamları: 1138
siniftur2 değişken toplamları: 1138

Program, sinifana adlı bir sınıf ve bu sınıftan public olarak türetilen siniftur1 ve siniftur2 adlı iki adet sınıf oluşturur. Her üç sınıf içinde de private, protected ve public olmak üzere üç adet değişken ve bu değişkenlere değer atayan, ekranda gösteren ve değerlerini toplayan üç adet fonksiyon tanımlar.

Program, sinifana türünden pnes_ana adlı bir işaretçi ve nes_ana adlı bir nesne ile siniftur1 türünden nes_tur1 adlı ve siniftur2 türünden nes_tur2 adlı bir nesne oluşturur.

İlk olarak, nes_ana nesnesinin bellek adresini pnes_ana işaretçisine atar. Bu işaretçi ile, sinifana içindeki deger_ata() ve deger_goster() fonksiyonları ile sinifana içindeki değişkenlere birer değer atar ve ekrana yazar. İşaretçi ile deger_topla() sanal fonksiyonunu kullanarak, sinifana içindeki değişken değerlerini toplar. Sonra, aynı işlemi nes_ana nesnesi yoluyla yapar.

İkinci safhada, nes_tur1 nesnesi yoluyla siniftur1 içindeki deger_ata_tur1() ve deger_goster_tur1() fonksiyonları ile siniftur1 içindeki değişkenlere birer değer atar ve ekrana yazar. Sonra, nes_tur1 nesnesinin bellek adresini pnes_ana işaretçisine atar. İşaretçi ile siniftur1 içindeki deger_topla() sanal fonksiyonunu kullanarak, siniftur1 içindeki değişken değerlerini toplar. Sonra, aynı işlemi nes_tur1 nesnesi yoluyla yapar.

Son olarak, nes_tur2 nesnesi yoluyla siniftur2 içindeki deger_ata_tur2() ve deger_goster_tur2() fonksiyonları ile siniftur2 içindeki değişkenlere birer değer atar ve ekrana yazar. Sonra, nes_tur2 nesnesinin bellek adresini pnes_ana işaretçisine atar. İşaretçi ile siniftur2 içindeki deger_topla() sanal fonksiyonunu kullanarak, siniftur2 içindeki değişken değerlerini toplar. Sonra, aynı işlemi nes_tur2 nesnesi yoluyla yapar.

Her üç sınıf içinde ayrı ayrı tanımlanmış olan deger_topla() sanal fonksiyonu, sinifana sınıfı türünden tanımlanmış işaretçiye her sınıftan oluşturulmuş nesnelerin bellek adresleri atandığında, işaretçinin her sınıfın sanal fonksiyonuna erişerek işlem yapmasını sağlar. Ana sınıf olan sinifana işaretçisi ile sanal fonksiyon çağrıldığında, derleyici ana sınıf veya türetilmiş sınıf içindeki sanal fonksiyonlardan hangisini kullanacağına çalışma zamanında karar verir. Bu özelliğe çalışma zamanı çok biçimliliği (Runtime Polymorphism) denir.

Diğer taraftan, sinifana sınıfı türünden tanımlanmış işaretçiye türetilen sınıflardan oluşturulmuş nesnelerin bellek adresleri atansa bile, işaretçi sanal fonksiyonlar dışındaki elemanlara erişim sağlayamaz.

Programın çalışmasını gösteren şema aşağıdadır: