C Pointers [tr]

Default olarak, C++ compile time’da bir fonksiyon çağrısını bu fonksiyonun doğru tanımı ile karşılaştırır. Bu işleme static binding denir. Programcı bu işlemi run time da yapabilir, bu işleme de dynamic binding adı verilir. Eğer programcı dynamic binding yapmak istiyorsa, fonksiyonun başına virtual yazarak yapabilir.

Aşağıdaki örneklerde static ve dynamic binding’ler gösterilmektedir.

#include “iostream”
using namespace std;

struct A {
  void f() { cout << "Class A" << endl; }
};

struct B: A {
  void f() { cout << "Class B" << endl; }
};

void g(A& arg) {
  arg.f();
}

int main() {
  B x;
  g(x);
}


bu kod parçasının output’u ‘Class A’ olacaktır. g fonksiyonu çağırıldığı zaman içerisinde object’e B tipine sahip olsa dahi A tipinin f fonksiyonu çağrılır. Compile time’da compiler g fonksiyon argümanının A’dan türeyen bir object’e referans ettiğini bilir. Compiler argümanın A tipinde mi B tipinde mi olduğuna karar veremez. Öte yandan bu olay run time’da yapılabilir. Aşağıdaki kod örneği A::f() fonksiyonunun virtual olması dışında tamamen yukarıdaki kod örneği ile
aynıdır.

#include “iostream”
using namespace std;

struct A {
  virtual void f() { cout << "Class A" << endl; }
};

struct B: A {
  void f() { cout << "Class B" << endl; }
};

void g(A& arg) {
  arg.f();
}

int main() {
  B x;
  g(x);
}


yukarıdaki kod parçasının çıktısı ‘Class B’ olacaktır. Virtual keyword’u argümanın tipine bakmaksızın f fonksiyonunun tanımını seçer ama object’nin tipi yine B tipine referans eder.

Böylelikle programcı diğer türemiş class’lar için tekrar tanımladığı memberr function olan virtual fonksiyon, compiler tarafından aldığı argümana göre yeniden tanımlanacaktır. Hatta programcı argüman tipinin base class’ına referans eden bir pointer’la dahi fonksiyonu çağırsa da işlem yine aynı olacaktır.

Virtual fonksiyonu declare eden veya inherit eden class ‘polymorphic class’ olarak çağrılır.

Programcı herhangi bir türetilmiş class içerisinde member function olan bir virtual fonksiyon yazabilir. Mesela; A class’ı içerisinde bir virtual fonksiyon tanımladık ve B class’ı A class’ını direct ya da indirect şekilde türetiyor. Eğer B class’ı içerisindeki f fonksiyonu A class’ı içerisindeki f fonksiyonu ile aynı isim ve aynı parametre listesine sahipse yani imzaları aynı ise B class’ının da f fonksiyonu virtual’dır. (B class’ı içerisindeki f fonksiyonu virtual olarak declare edilmesi bile) Öte yandan B class’ı içerisindeki f fonksiyonu A class’ı içerisinde f fonksiyonu ile aynı isimlere sahip olsalar dahi parametre listesi farklı olduğunda B class’ı içerisindeki f fonksiyonu, A class’ı içerisindeki f fonksiyonunu override etmiş olmaz ve bu nedenle virtual olamaz. (B class’ı içerisinde f fonksiyonu virtual olarak declare edilmiş olsa bile)

#include “iostream”
using namespace std;

struct A {
  virtual void f() { cout << "Class A" << endl; }
};

struct B: A {
  void f(int) { cout << "Class B" << endl; }
};

struct C: B {
  void f() { cout << "Class C" << endl; }
};

int main() {
  B b; C c;
  A* pa1 = &b;
  A* pa2 = &c;

  pa1->f();
  pa2->f();
}


Yukarıdaki kod parçasının çıktısı ;

Class A
Class C


olacaktır. Çünkü B class’ı içerisinde f fonksiyonu virtual değildir ve A class’ı içerisindeki f fonksiyonunu gölgeler. Bu sebeple compiler b.f() çağrısına izin vermeyecektir. C class’ı içerisindeki f fonksiyonu virtual’dır, A içerisindeki f fonksiyonunu C class’ı içerisinde görünmez olsa bile f fonksiyonunu override eder.

Programcı base class içerisindeki destructor virtual olarak tanımlarsa türetilmiş class’ların da destructuor’ı base class’ın destructor’ını override etmiş gibi olacaktır. (Destructor’ların override edilemeyeceği gerçeğine rağmen) Override edilen virtual fonksiyonun return tipi override eden virtual fonksiyonun return tipinden farklı olabilir. Bu durumda override edilen fonksiyona ‘covariant virtual fonksiyon’ adı verilir. B class’ı A class’ının virtual f fonksiyonunu override ediyor fakat return tipleri farklı ise aşağıdaki durumlar oluşabilir.

B class’ının f fonksiyonu T tipindeki bir object’nin reference’nı ya da pointer’ını return eder ve A class’ının f fonksiyonu da açık olarak direct ya da indirect şekilde T class’ının base class’ı olan class’ın reference’ını ya da pointer’ı return eder.B class’ının f fonksiyonu tarafından return edilen const veya volatile pointer ya da reference niteliği, A class’ının f fonksiyonu tarafından return edilen reference ya da pointer’ın niteliğinden daha az veya aynıdır. B class’ının f fonksiyonunun return tipi ya B declaration’ının içinde olmalı (f fonksiyonu implement edilmeli) veya B tipinde olmalıdır.

#include “iostream”
using namespace std;

struct A { };

class B : private A {
  friend class D;
  friend class F;
};

A global_A;
B global_B;

struct C {
  virtual A* f() {
    cout << "A* C::f()" << endl;
    return &global_A;
  }
};

struct D : C {
  B* f() {
    cout << "B* D::f()" << endl;
    return &global_B;
  }
};

struct E;

struct F : C {

  // Error:
  // E is incomplete
  // E* f();
};

struct G : C {
  // Error:
  // A is an inaccessible base class of B
  // B* f();
};

int main() {
  D d;
  C* cp = &d;
  D* dp = &d;

  A* ap = cp->f();
  B* bp = dp->f();


  return 0;
};


Yukarıdaki kod parçasının ekran çıktısı ;

B* D::f()
B* D::f()


bu şekilde olacaktır.

A* ap = cp->f() ifadesi D::f() fonksiyonunu çağırır ve pointer’ı A* tipine dönüştürür. B* bp = dp->f() ifadesi de D::f() fonksiyonunu çağırır ama return tipini B* tipine dönüştürmez. Compiler virtual F::f() fonksiyonunu tanımlamaya izin vermeyecektir çünkü E complete bir sınıf değildir. Compiler G::f() fonksiyonunun tanımına da izin vermez çünkü A sınıfı, B sınıfının erişilebilir bir base class’ı değildir. (D ve F sınıfı arkadaş sınıf olmasına rağmen B sınıfı G sınıfına member’larına erişme izni vermez) Virtual fonksiyonular global veya static olamazlar çünkü tanım olarak virtual fonksiyon bir base class’ın member function’ıdır ve fonksiyon implementasyonunun kim tarafından çağırıldığının belirlenmesi ilkesine dayanır. Programcı, virtual fonksiyonu başka bir class’ın arkadaşı olabilmek için tanımlayabilir.

Eğer fonksiyon base class içerisinde virtual olarak tanımlanmışsa, programcı binary resolution operator(::) ile kullanarak direk olarak fonksiyona erişebilir. Bu durumda virtual fonksiyon çağrı mekanizması engellenir ve base class içerisinde tanımlanmış fonksiyon implementasyonu kullanılır. Ek olarak, programcı türetilmiş class içerisinde virtual member fonksiyonu override etmemişse, fonksiyon base class içerisindeki fonksiyon içeriği direk olarak alarak, bu içeriği kullanır.

Virtual fonksiyolar ;

Implement edilebilir,
Pure olarak declare edilebilir,
Hem implement edilip hem de pure olarak declare edilebilir.
Base class bir ya da daha fazla pure virtual fonksiyon içeriyorsa ‘abstract class’ olarak adlandırılır.

Kaynak Bilindiği üzere C dilinde int, double ,char vb. tiplerde bir pointer oluşturabiliyorduk ve bu pointerlar vasıtası ile adresler üzerinde istediğimiz işlemleri yapabiliyorduk. Fakat biz şunu biliyoruz ki adresler üzerinde yapılan işlemler diğer sıradan işlemlere göre biraz daha bilgi isteyen ve biraz daha riskli işlemlerdir. Bu nedenle bu makalenin ağırlığı daha çok bu konu üzerine olacak. 

C dilinde adresleme konusunu ikiye ayırabiliriz. 
1 ) static adresleme ( array, const ... )
2 ) dynamic adresleme ( pointer )

Bunun en güzel örneği array ile pointer arasındaki farktır. Bir array’in boyutu program başlamadan verilir ve program sonuna kadar değiştirilemez.

Örneğin;

int arr[10]; şeklinde boyutu 10 olan bir integer array tanımladık fakat işlemlerimiz ilerleyince anladık ki bize 11 tane eleman lazım. Peki biz
int arr[11] = 7

şeklinde bir atama yapabilir miyiz ? C dili böyle bir atamaya izin vermez. Bize ait olmayan bir alana erişemeyiz. Bu bir segmentation fault’dur ve compile time’da karşımıza çıkar. İşte pointer farkı burada işimize yarıyor :)

Malloc & Free

Malloc aldığı parametre kadarlık alanı allocate eden bir fonksiyondur ve pointer’a yer almada kullanılır. Free ise aldığımız alanı işletim sistemine geri vermek için kullandığımız bir başka fonksiyondur. Hemen bir örnekle işin esprisini çözelim. 



Görüldüğü üzere boyutu 10 olan bir integer pointer’ının boyutunu 11 yaptık. İşte pointerın en güzel yanı bu, yani dinamik programlama. İstediğimiz an alanı genişletip, istediğimiz an alanı daraltabiliriz. Hafızayı deallocate ederken de free fonksiyonunu kullandık. İşin tehlikeli yanı burası. Eğer büyük bir program yazıyorsak ve aldığımız alanları geri vermediysek
bir süre sonra run-time’da stack overflow hatası alabiliriz ki bu hata bir programcının logic hatalar ile birlikte en çok korktuğu hataların başında gelir.

MALLOC ve FREE hakkında daha geniş bilgi için :
http://www.cplusplus.com/reference/clibrary/cstdlib/malloc/
http://www.cplusplus.com/reference/clibrary/cstdlib/free/


VOID POINTER

Fundamental ( temel ) tiplerde pointer’ları görmüştük. Bir integer pointer’ına integer değerler, bir char pointer’ına char değerleri atayabiliyoduk. İyi ama void türünde bir pointer’a biz ne atıyacağız ?
Void pointer, üzerinde herhangi bir aritmetik işlem yapamadığımız ve indexleyemediğimiz, sadece casting işlemlerini gerçekleştirmek için kullanılan pointer türüdür. 
Bir float pointer üzerinde yürüyebiliyorduk veya bu pointer’ı başka bir float pointer ile kıyaslayabiliyorduk. Bu işlemleri void pointer üzerinde yapamayız fakat void pointer’ın öyle bir artısı var ki coder’ları çok büyük zahmetlerden kurtarıp, dilin esnekliğini artırıyor. Bir
void pointer’ına tipi ne olursa istediğimiz başka bir pointer’ı atayabiliriz. Peki bu bize ne sağlar bir örnek üzerinde inceleyelim.


void * bsearch ( const void * key, const void * base, size_t num, size_t size, int ( * comparator ) ( const void *, const void * ) ); [\\\\FONT]

Yukarıdaki prototype çok bilindik bir fonksiyon olan binary search fonksiyonunun prototype’dır. Prototype’da da görüldüğü üzere bu fonksiyon const void pointer’lar
alıyor fakat bizim bütün fundamental tiplerimiz üzerinde çalışıyor. 


Örneğin;



Görüldüğü gibi bir tane comparator function sayesinde istediğimiz tipi bsearch fonksiyonu sayesinde sıralayabiliyoruz. Bu fonksiyon belki 30 belki 40 sene önce yazıldı ama hala işimizi
görebiliyor. İşte bu void pointer’ın artısı ;)

BINARY SEARCH hakkında daha geniş bilgi için :

http://www.cplusplus.com/reference/clibrary/cstdlib/bsearch/

CASTING

Binary search fonksiyonunu kullanırken ekstradan bir compare fonksiyonu yazdık ve içerisinde iki pointer arasındaki farkı return ettik. Fonksiyona gelen değerler
const void pointer ve biz dedik ki void pointer’lar üzerinde işlem yapamıyoruz. Peki biz bunların arasındaki farkı nasıl aldık ozaman ? :)


Casting, farklı tiplerdeki iki değişkeni birbirine atamak için kullanılan metottur.
Örneğin;



Yukarıdaki kodda görüldüğü gibi integer olan değeri double türündeki bir değere cast ettik ve ekrana bastırdık. Aynı void pointer’ın integer’a veya double’a cast edildiği gibi. Bu kadar kolay :) ( Eğer biz burada integer’ı cast etmeyip double’a direk atasaydık da bu işlemin sonucu değişmeyecekti, çünkü fundamental tiplerin birbirine atanması olayı compiler tarafından otomatik cast ediliyor. C ’de cast işlemleri compareints ve comparedoubles fonksiyonlarında olduğu gibi ağırlıklı olarak pointer’lar üzerinde kullanılır. ) 

FONKSİYON POINTER

Dikkat ettiyseniz binary search fonksiyonunun prototype’ında farklı bir parametre vardı. 
int ( * comparator ) ( const void *, const void * )[\\\\FONT]

İşte bu bir fonksiyon pointer’ıdır. Bir fonksiyon pointer’ı yazmanın bildiğimiz fonksiyon yazmadan hiçbir farkı yoktur. Tek fark onu kendisine parametre olarak alacak fonksiyon prototype’ndadır. Bunun örneğini de binary search prototype’ında görmüştük. Binary search örneğini hatırlarsak eğer hem double hem de integer için çalışmıştı. Çünkü biz orada bir fonksiyon pointer’ı kullandık. Eğer sıralamak istediğimiz tip integer ise compareints, double ise comparedoubles fonksiyon pointer’ını çağırdık. Fonksiyon pointer’ı dediğimiz şey bu işe yarar. Kendisi bir pointer olduğu için başka fonksiyonlara parametreymiş gibi yollayabiliriz.

Popular posts from this blog

Polya’nın Problem Çözme Teknikleri

Mikroislemci Temelleri