C de Veri Tipleri

Yapıcı ve Yıkıcı Fonksiyonlar (Constructor & Destructor):

Bir sınıftan bir nesne üretildiği anda otomatik olarak çalışan fonksiyonlara yapıcı fonksiyonlar denir. Benzer şekilde bir sınıftan üretilmiş olan nesne yok edildiği anda otomatik olarak çalışan fonksiyona da yıkıcı fonksiyon denir.

Yapıcı ve yıkıcı fonksiyonlar sınıf adı ile aynı isme sahip olmakla beraber yıkıcı fonksiyon adının önünde tilde "~" işareti de ayrıca olmak zorundadır. Ayrıca bu fonksiyonların dönüş değeri yoktur. Bu nedenle bu fonksiyonların başında int, void gibi ifadeler yer almaz.

C++:
class personel
{
private:
    //...

public:
    // constructor / yapıcı fonksiyon;
    personel()
    {
        //...
    }
    // destructor / yıkıcı fonksiyon;
    ~personel()
    {
        //...
    }
};


Yapıcı fonksiyonun çalıştığını gösteren basit bir örnek verelim;
C++:
#include "stdio.h"
class personel
{
    private:
    
        char name[20];
        int  yovmiye;
 
    public:
        personel()
        {
            printf("Yapıcı Fonksiyon otomatik çalıştı. \n");
        }
   
};


int main()
{
    personel per1;
    personel *per2= new personel();
}
Program Çıktısı:
Yapıcı Fonksiyon otomatik çalıştı.
Yapıcı Fonksiyon otomatik çalıştı.

Personel sınıfından bir nesne (per1) ürettiğimiz zaman yapıcı fonksiyonumuzun çalıştığına dair bir çıktı alıyoruz. Pointer kullanarak bir nesne (per2) ürettiğimizde de aynı şekilde yapıcı fonksiyonumuzun çalıştığını görüyoruz.



Yıkıcı fonksiyonu da dahil ederek örneğimizi genişletelim;
C++:
#include "stdio.h"

class personel
{
    private:
   
        char name[20];
        int  yovmiye;
 
    public:
        personel()
        {
            printf("Yapıcı Fonksiyon otomatik çalıştı. \n");
        }

        ~personel()
        {
            printf("Yıkıcı Fonksiyon otomatik çalıştı. \n");
        }
  
};



int main()
{
    personel per1;

}
Program Çıktısı:
Yapıcı Fonksiyon otomatik çalıştı.
Yıkıcı Fonksiyon otomatik çalıştı.

per1 nesnemizi oluşturduğumuz zaman yapıcı fonksiyonun çalıştığını görüyoruz. Ardından program sonlandığı için yıkıcı fonksiyonumuzda otomatik olarak çalışıyor.

Şimdi şu örneğe bakalım;
C++:
#include "stdio.h"

class personel
{
    private:
    
        char name[20];
        int  yovmiye;
 
    public:
        personel()
        {
            printf("Yapıcı Fonksiyon otomatik çalıştı. \n");
        }

        ~personel()
        {
            printf("Yıkıcı Fonksiyon otomatik çalıştı. \n");
        }
  
};



int main()
{
    personel per1;
    personel *per2= new personel();

}

Program Çıktısı:
Yapıcı Fonksiyon otomatik çalıştı.
Yapıcı Fonksiyon otomatik çalıştı.
Yıkıcı Fonksiyon otomatik çalıştı.

Program çıktısından görüleceği üzere per1 ve per2 nesnesi için yapıcı fonksiyon çalışırken yıkıcı fonksiyon sadece per1 nesnesi için çalışıyor. Bunun nedeni per2 nesnesinin pointer kullanılarak hafızanın "heap" bölümünde oluşturulmuş olmasıdır. Bu durumda per2 nesnesi için de yıkıcı fonksiyonu çalıştırmak istersek delete komutu ile per2 nesnesi yok edilmelidir. Örneğimiz ile görelim;

C++:
#include "stdio.h"

class personel
{
    private:
    
        char name[20];
        int  yovmiye;
 
    public:
        personel()
        {
            printf("Yapıcı Fonksiyon otomatik çalıştı. \n");
        }

        ~personel()
        {
            printf("Yıkıcı Fonksiyon otomatik çalıştı. \n");
        }
  
};



int main()
{
    personel per1;
    personel *per2= new personel();

    delete per2;

}
Program Çıktısı:
Yapıcı Fonksiyon otomatik çalıştı.
Yapıcı Fonksiyon otomatik çalıştı.
Yıkıcı Fonksiyon otomatik çalıştı.
Yıkıcı Fonksiyon otomatik çalıştı.


Yapıcı ve yıkıcı fonksiyonların hangi şartlarda çalıştığını örneklemeye çalıştım. Ek olarak yapıcı ve yıkıcı fonksiyonlar sınıf bloğu dışında (prototip olmak şartı ile) tanımlanabilir. Örnekleyelim;
C++:
#include "stdio.h"

class personel
{
    private:
      
        char name[20];
        int  yovmiye;
 
    public:
        personel();
        ~personel();
 
};

personel::personel()
{
    printf("Yapıcı Fonksiyon otomatik çalıştı. \n");
}

personel::~personel()
{
    printf("Yıkıcı Fonksiyon otomatik çalıştı. \n");
}


int main()
{
    personel per1;
    personel *per2= new personel();

    delete per2;
}
 
Son düzenleme:
Yapıcı Fonksiyonlar ile Nesnelere Değer Atama:

Yapıcı fonksiyonların kullanım amaçlarından biri sınıftan üretilen nesnelerin ilk değerlerinin atanmasıdır. Aşağıdaki örnekte personel sınıfında bir nesne oluşturulunca personel adı varsayılan olarak yeni personel şeklinde atanır.

C++:
#include "stdio.h"

class personel
{
    private:
        // isim private dışardan erişelemez.
        const char *name;
        int  maas;
   
    public:
        personel()
        {
            name ="Yeni Personel";
            maas = 3000;
            printf("Yapıcı Fonksiyon çalıştı. \n");
        }

        void showname()
        {
            printf("Personel Adı : %s \n",name);
        }

        void setname(const char *isim)
        {
          name = isim;
        }
       
   
};




int main()
{
    personel per1;

    per1.showname();

    per1.setname("Ali");

    per1.showname();
}
Program Çıktısı:
Yapıcı Fonksiyon çalıştı.
Personel Adı : Yeni Personel
Personel Adı : Ali

Örnekte personel adı daha sonra setname fonksiyonu ile Ali olarak değiştirilmiştir.

Yapıcı Fonksiyonlarda Başlatma Listesi (initializer list) :


Yapıcı fonksiyonun gövdesi içerisindeki atmalar başlatma listesi özelliği sayesinde tanımlama sırasında da yapılabilir. Önce örneği inceleyim;
C++:
#include "stdio.h"

class Stoklar
{
    protected:
        int cimento; // yaz kızım;
        int demir;
    public:
    
    Stoklar(int c_miktar,int d_miktar)
    {
        cimento=c_miktar;
        demir= d_miktar;
    }
    void rapor()
    {
        printf("Çimento = %d \n",cimento);
        printf("Demir = %d \n",demir);
    }
};

int main()
{
    Stoklar Stk_1(10,50);
    Stk_1.rapor();
}
Program Çıktısı:
Çimento = 10
Demir = 50

yapıcı fonksiyonu başlatma listesi özelliği ile yazmak istersek yapı aşağıdaki gibi oluşturulur.
C++:
    Stoklar(int c_miktar,int d_miktar):cimento(c_miktar),demir(d_miktar)
    {
       // boş olsada gövde "{ }" olmak zorunda...
    }

Bu alternatif kullanım aynı görevi örmektedir. Çoğu durumda ha initializer list kullanılmış, ha değişkenlere blok içinde değer atamış fark etmektedir. Ancak bazı durumlarda nesnenin değişkenlerine initializer list ile değer vermek gerekir. Örneğin değişken const veya referans ise, değer ataması ancak bu şekilde yapılabilir. (@taydin ;) )

Yapıcı fonksiyondan başka bir Yapıcı fonksiyonunun çağrılması ( Constructor Delegation ) :

Özellikle kalıtım konusunda türetilmiş bir sınıftan nesne oluştururken üst sınıfın yapıcı fonksiyonunun nasıl davranacağı konusunda çözüm sağlayan faydalı bir özelliktir. initializer list ile aynı şekilde bir yapıda kullanılır.

C++:
class taseron:public personel
{
  public:
    taseron(const char *isim, int bakiye=0):personel(isim,bakiye)
    {
      //..
    }
}

Örnek te taseron sınıfı personel sınıfından türetilmiş bir sınıftır. taşeron sınıfıın yapıcı fonksiyonu çalışmadan önce personel sınıfının yapıcı fonksiyonuna arguman gönderir. Önce personel sınıfının sonra taseron sınıfının yapıcı fonksiyonu çalışır. Şimdilik biraz karışık gelmiş olabilir. Kalıtım konusunda örneklerler beraber konu pekişecektir.

This Pointer :

Sınıfın kendi değişkenini vurgulamak için kullanılan özel bir pointer türüdür. Yukarıdaki örnekte personel sınıfına ait olan name değişkenine setname fonksiyonunun aldığı isim parametresi atanmıştır. setname fonksiyonunun parametre adı da isim olsa idi derleyeci hangi isim değişkeninin kime ait olduğu konusunda hata verecekti. This pointer kullanılarak fonksiyonların aldığı parametreler ile sınıfın değişkenlerinin aynı isimlendirme ile kullanımı mümkün olur. Bu durumda setname fonksiyonu şu şekilde yazılmalıdır;

C++:
        void setname(const char *name)
        {
          this->name = name;
        }
 
Son düzenleme:
Parametre alan Yapıcı Fonksiyonlar :

Yapıcı fonksiyonların parametre alması konusunda özel bir durum yoktur. Normal fonksiyonlar gibi parametre alabilirler. Bu sebepten istenir ise sınıfın niteliklerine nesne oluşturulurken değer atanabilir.

C++:
#include "stdio.h"

class personel
{
    private:
        // isim private dışardan erişelemez.
        const char *name;
        int  maas;
  
    public:
        personel(const char *name,int maas)
        {
            this->name = name;
            this->maas =maas;
            printf("Yapıcı Fonksiyon çalıştı. \n");
        }

        void showinfo()
        {
            printf("Personel Adı : %s  Personel Maası : %d TL. \n",name,maas);
          
        }
 
};




int main()
{
    personel per1("Mehmet",1000);

    personel *per2= new personel("Veli",2000);

    per1.showinfo();
    per2->showinfo();


}
Program Çıktısı:
Yapıcı Fonksiyon çalıştı.
Yapıcı Fonksiyon çalıştı.
Personel Adı : Mehmet  Personel Maası : 1000 TL.
Personel Adı : Veli  Personel Maası : 2000 TL.


Bu kodlamada bir noktaya dikkat çekmek istiyorum. Yukarıdaki kodda, aşağıdaki gibi per3 veya per4 nesnelerini oluşturmak istersem;
C++:
 personel per3;
    // veya
    personel *per4= new personel();

Derleyici ; "personel" sınıfının varsayılan oluşturucusu yok. Hatasını veriyor. Halbuki önceki örneklerde bu şekilde nesneleri sorunsuz üretebiliyorduk...Hata mesajında oluşturucu ile kastedilen şey yapıcı fonksiyon. Bir sınıf içerisinde yapıcı fonksiyon tanımlamasak da bu şekilde nesne üretebiliyorduk... Bir sınıf oluşturulduğunda yapıcı fonksiyon tanımlanmasa bile arka planda çalışan gizli bir yapıcı fonksiyon mevcuttur.

C++:
#include "stdio.h"

class personel
{
    private:
        // isim private dışardan erişelemez.
        const char *name;
        int  maas;
  
    public:
    /* gizli
    personel()
    {
        // içi boş..
    }
    */
};

int main()
{
    personel *per1= new personel();

}

C dilinde parantez demek fonksiyon demekti... O halde biz new personel(); dediğimizde aynı zamanda sınıfın gizli olan yapıcı fonksiyonunu çağırmış oluyoruz. Biz sınıf içerisinde yapıcı fonksiyon tanımlarsak artık gizli olan yapıcı fonksiyon görünür hale geliyor ve fonksiyon içerisine istediğimiz kodlamayı yapıyoruz. İlk örnekte yapıcı fonksiyona parametre verdiğimiz için artık parametresi olmayan,
C++:
 personel per3;
    // veya
    personel *per4= new personel();
satırları hata vermektedir. Çünkü bu satırları karşılayan bir fonksiyon bulunmamaktadır....



Ayrıca biz sınıftan bir nesne üretirken her koşul altında aslında yapıcı fonksiyonu çağırıyor isek;
C++:
 personel per3;
ifadesi,
C++:
 personel per3();
şeklinde de yazılabilir.
 
Yapıcı Fonksiyonların Aşırı Yüklenmesi (Constructor overloading):

Yapıcı fonksiyonlara parametre verebildiğimize göre birden çok yapıcı fonksiyon oluşturularak fonksiyon aşırı yüklemesi mümkündür.
Bir önceki örnekte parametre alan tekbir yapıcı fonksiyon olduğu için artık argüman vermeden yeni bir nesne oluşturamıyorduk. Hadi bir örnek ile bu yapıcı fonksiyonu aşırı yükleyerek durumu çözelim...

C++:
#include "stdio.h"

class personel
{
    private:
        // isim private dışardan erişelemez.
        const char *name;
        int  maas;
    
    public:
        personel()
        {
            this->name ="Yeni Personel";
            this->maas = 0;
            printf("Normal Yapıcı Fonksiyon çalıştı. \n");
        }
        
        personel(const char *name,int maas)
        {
            this->name = name;
            this->maas =maas;
            printf("Parametre alan Yapıcı Fonksiyon çalıştı. \n");
        }

        void showinfo()
        {
            printf("Personel Adı : %s  Personel Maası : %d TL. \n",name,maas);
            
        }
    
};




int main()
{
    personel per1("Mehmet",1000);

    personel *per2= new personel("Veli",2000);

    personel per3;

    personel *per4 = new personel();

    per1.showinfo();
    per2->showinfo();
    per3.showinfo();
    per4->showinfo();

}
Program Çıktısı:
Parametre alan Yapıcı Fonksiyon çalıştı.
Parametre alan Yapıcı Fonksiyon çalıştı.
Normal Yapıcı Fonksiyon çalıştı.
Normal Yapıcı Fonksiyon çalıştı.
Personel Adı : Mehmet  Personel Maası : 1000 TL.
Personel Adı : Veli  Personel Maası : 2000 TL.
Personel Adı : Yeni Personel  Personel Maası : 0 TL.
Personel Adı : Yeni Personel  Personel Maası : 0 TL.
 
Kopyalayıcı Yapıcı Fonksiyonlar ( Copy constructor ) :

Sınıftan yeni bir nesne oluştururken daha önceden aynı (veya uygun) sınıftan oluşturulmuş bir nesnenin verilerini yeni oluşturulan nesneye kopyalayan yapıcı fonksiyondur. Kafa karışıklığını azaltmak için normal bir fonksiyonun yapısını hatırlayalım;
C++:
Fonksiyon Adi (VeriTipi ParametreAdi);
Fonksiyonun aldığı parametrenin veri türünü (int, char vs) fonksiyonu tanımlarken bildiriyoruz. Nesnelerde bir veri türü olduğu için bir nesneyi bir fonksiyona parametre olarak göndermek mümkündür. Örneğin personel sınıfından bir nesneyi bir fonksiyona gönderiyorsak fonksiyon tanımı şu şekilde olur;
C++:
Fonksiyon Adi (personel *a_nesnesi) // nesnenin adresi gönderilir

O halde bir nesneyi parametre olarak alan bir yapıcı fonksiyon yazıp nesnenin değerlerini yeni fonksiyona aktarırsak kopyalayıcı yapıcı fonksiyon kullanmış oluruz.

Örnekleyelim;
C++:
#include "stdio.h"

class personel
{
    private:

        const char *unvan;
        int  maas;
   
    public:
        personel()
        {
            this->unvan="Vasıfsız";
            this->maas = 1000;
            printf("Normal Yapıcı Fonksiyon çalıştı. \n");
        }
       
        personel(const char *unvan,int maas)
        {
            this->unvan = unvan;
            this->maas =maas;
            printf("Parametre alan Yapıcı Fonksiyon çalıştı. \n");
        }
        // copy instructor....
        personel (personel *referans_nesne)
        {
            this->unvan = referans_nesne->unvan;
            this->maas = referans_nesne->maas;
            printf("Kopyalayıcı Yapıcı Fonksiyon çalıştı. \n");
        }
        void showinfo()
        {
            printf("Personel Ünvanı : %s  Personel Maası : %d TL. \n",unvan,maas);
           
        }
   
};



int main()
{
    personel per1;  // varsayılan vasıfsız 1000 tl maas.

    personel *per2= new personel("Mudur",2000);

    personel per3(per2);

    per1.showinfo();
    per2->showinfo();
    per3.showinfo();


}
Program Çıktısı:
Normal Yapıcı Fonksiyon çalıştı.
Parametre alan Yapıcı Fonksiyon çalıştı.
Kopyalayıcı Yapıcı Fonksiyon çalıştı.
Personel Ünvanı : Vasıfsız  Personel Maası : 1000 TL.
Personel Ünvanı : Mudur  Personel Maası : 2000 TL.
Personel Ünvanı : Mudur  Personel Maası : 2000 TL.

Not: Kod içerisinde nesneler üretilirken görsel alışkanlık olması açısından hem hem stack hemde heap (pointer ile) kısmında nesne oluşacak şekilde karışık kullanım yapılmaktadır...
 
Ön Not : Static değişken kullanımını şurada göstermiştim;

Sınıfların Statik Nitelikleri (Static Attributes):

Sınıflarda static değişken kullanımı fonksiyonlardaki static değişken kullanımına benzerlik gösterir. Bir sınıftan bir nesne ilk defa oluşturulduğunda hafızada static değişken oluşur ve sonrasında oluşan tüm nesneler için ortak kullanılır. Örnek ile konuyu daha net anlayalım;

C++:
#include "stdio.h"

class personel
{
    private:
        const char *unvan;
        int  maas;
  
    public:
        static int perSay;

        personel()
        {
           perSay++;  // her nesne oluştuğunda sayıyı arttır.
           printf("Yeni Personel Eklendi... \n");
        }


  
};

// ilklendirme sınıf dışında yapılır...
int personel::perSay=0;
// aslında olayı global bir değişkene bağlamış oluyoruz...

int main()
{
    personel p1,p2,p3;  // 3 personel (nesne) oluştur.

    //static değişkene doğrudan sınıf adı ile ulaşılabilir.

    printf("Toplam Personel : %d \n",personel::perSay);
  
}
Program Çıktısı:
Yeni Personel Eklendi...
Yeni Personel Eklendi...
Yeni Personel Eklendi...
Toplam Personel : 3

perSay değişkenine nesneler üzerinden de ulaşmak mümkündür. Nesnelerin kaldırılma ihtimaline karşı sınıf üzerinden ulaşmak daha mantıklıdır...

Sınıfların Statik Metotları (Static Methods):

Değişkenlerde olduğu gibi fonksiyonların da başına static ifadesi eklenir ise artık o fonksiyon sınıfa özgü genel bir method (fonksiyon) olur ve sınıf adı üzerinden dışarıdan çağırılabilir. Örnekleyelim;
C++:
#include "stdio.h"

class personel
{
    private:
        const char *unvan;
        int  maas;
   
    public:
        static int perSay;

        personel()
        {
           perSay++;  // her nesne oluştuğunda sayıyı arttır.
           printf("Yeni Personel Eklendi... \n");
        }

        static void bilgiver()
        {
            printf("Toplam Personel Sayısı : %d \n",perSay);
        }


   
};

// ilklendirme sınıf dışında yapılır...
int personel::perSay=0;
// aslında olayı global bir değişkene bağlamış oluyoruz...

int main()
{
    personel p1,p2,p3;  // 3 personel (nesne) oluştur.

    personel::bilgiver();

}
Program Çıktısı:
Yeni Personel Eklendi...
Yeni Personel Eklendi...
Yeni Personel Eklendi...
Toplam Personel Sayısı : 3
 
Son düzenleme:
Sınıflar ile Const Tipinin Kullanımı :

Konuya girmeden önce const kullanımı hatırlayalım;
Kod:
int main()
{
    const int i=5;
    i=6;  // Hata Verir!!!
}
Bir veri türünün başına const ifadesi eklenir ise sadece tanımlama sırasında değer atanabilir. Sonrasında değer read only olarak çalışır. Sonradan değer atanmaya kalkışılır ise derleyici hata verir.
C++:
#include "stdio.h"

void test(const int i)
{
    i=6; // Hata verir!!!
}

int main()
{
    int i=5;
    test(i);
}

Aynı şekilde fonksiyonun aldığı parametrenin başına const ifadesi eklenir ise fonksiyon içerisinde parametrenin değeri değiştirilemez.


Konuyu kısaca hatırlattıktan sonra sınıf ve nesneleri const ile ilişkilerine bakalım.

1. Const ön eki ile nesne oluşturma:

Bir sınıftan bir nesne üretilirken başa const ifadesi eklenir ise nesne oluşturulurken yapıcı fonksiyon ile aldığı varsayılan değerler bir daha değiştirilemez. Örnekte görelim;
C++:
#include "stdio.h"

class personel
{
    public:
        char *isim;
        int maas;
    personel()
    {
        isim = "Caylak";
        maas=0;
    }
};

int main()
{
    personel per1;

    per1.maas=300;

    const personel per2;

    per2.maas=500;  // hata verir...
}

2. Sınıf niteliklerinde const kullanımı:

Sınıf içerisindeki değişkenler tanımlanırken herhangi bir özel durum yoktur. Pointer şeklindeki karakter dizilerinde tanımlanan pointer aynı zamanda dizinin ilke elemanını gösterdiği için const ile tanımlanması pointer ın gösterdiği adresin yanlışlıkla değiştirilmesini engeller. Bu yönden const char *isim; şeklinde bir karakter dizi tanımlama uygun bir yazımdır. Ancak const int min_isci_Yasi=18; ile tanımlanan sabit değişkenin kullanımında kural dışı bir durum olmamakla beraber sınıflarda bu tarz bir kullanım mantıklı değildir. Tüm nesneler için sabit bir değer ile işlem yapılacaksa bu değer bir global değişken olarak tanımlanabilir. Böylece tüm nesnelerde bu değer için hafızada bir adet int değişkeni saklanır. Aksi takdirde oluşturulan tüm nesneler için ayrıca bir int kadar daha yer kullanılacaktır.
C++:
class personel
{
    public:
        const char *isim;
        int maas;
        const int min_isci_Yasi=18;
        
    personel()
    {
        isim = "Caylak";
        maas=0;
    }
};

3. Sınıf Fonksiyonlarında (methodlarında) Const kullanımı :

C++:
void test (int a) const  // const parantezin sağına yazılır.
{
//....
}
Sınıfa ait fonksiyonlara const ifadesi eklenir ise o fonksiyonun bir değeri değiştirmesi engellemiş olur.
C++:
#include "stdio.h"

class personel
{
    public:
        const char *isim;
        int maas;
        
        
    personel()
    {
        isim = "Caylak";
        maas=0;
    }

    void setmaas(int a) const
    {
        this->maas=a; // const ifadesi nedeni ile hata verir...
    }
};

int main()
{
    personel per1;

    per1.setmaas(3000); 
}

Son olarak; const tipi fonksiyonlardan sadece yine const tipi fonksiyonlar çağrılabilir. Buradaki ana fikir const olmayan bir fonksiyon çağrılsa idi dolaylı olarak bir değer değişikliği mümkün olabilirdi. Amaç her türlü değer değişikliğine yol açacak yöntemin önüne geçmek...
 
Arkadaş Fonksiyonlar ve Sınıflar (Friend function & friend class) :

Sınıf dışındaki bir fonksiyon sınıf içerisinde friend olarak prototip şeklinde tanımlanır ise dışarıdaki fonksiyon sınıfın private değerlerine erişim hakkına sahip olur. Örneğin;
C++:
#include "stdio.h"

class personel
{
    private:
        const char *isim;
        int maas;
       
    public:  
    personel()
    {
        isim = "Caylak";
        maas=0;
    }
    // Blok dışındaki bir fonksiyon friend olarak tanımlanıyor.
    // Bu sayede dışarıdaki fonksiyon private değerlere erişme hakkına sahip olur.
    friend void ZamYaP(personel *ref_nesne);
};

// friend fonksiyon
void ZamYaP(personel *ref_nesne)
{
    ref_nesne->maas=10000;
    printf("Personel Maası : %d",ref_nesne->maas);
 
}

int main()
{
    personel per1;

    ZamYaP(&per1);
}

Yine aynı şekilde sınıf içerisinde diğer bir sınıf arkadaş sınıf olarak tanımlanır ise, diğer sınıftan asıl sınıfın private değerlerine erişmek mümkün olur. Örnekleyelim;
C++:
#include "stdio.h"

class personel
{
    private:
        const char *isim;
        int maas;
        
    public:   
    personel()
    {
        isim = "Caylak";
        maas=0;
    }
    // Başka bir sınıf friend olarak tanımlanıyor.
    // Bu sayede diğer sınıf, bu sınıfın private değerlere erişme hakkına sahip olur.
    friend class Muhasebe;
};

// friend sınıf...
class Muhasebe
{
    public:
    static void zamyap(personel *ref_nesne)
    {
        ref_nesne->maas=5000;
        printf("Personelin Maası %d TL ye yükseltildi...\n",ref_nesne->maas);
    }
};



int main()
{
    personel per1;

    Muhasebe::zamyap(&per1);
}
 
Nesne Dizileri (object array):

Basit bir konu ama değinmekte fayda var. Tüm veri türleri ile diziler oluşturabiliyoruz. Nesnelerde bir veri türü olduğuna göre dizi şeklinde nesne oluşturmak mümkündür. Örnekleyelim;
C++:
#include "stdio.h"

class personel
{
    public:
       
        int maas;
};

int main()
{
    // 5 elemanlı bir nesne dizisi tanımlıyoruz...
    personel isciler[5];

    for (int i=0; i<5 ; i++)
    {
        isciler[i].maas= (i+1)*500;
        printf("%d. İşçinin Maaşı: %d TL. dir \n", i+1, isciler[i].maas);
    }
}
Program Çıktısı:
1. İşçinin Maaşı: 500 TL. dir
2. İşçinin Maaşı: 1000 TL. dir
3. İşçinin Maaşı: 1500 TL. dir
4. İşçinin Maaşı: 2000 TL. dir
5. İşçinin Maaşı: 2500 TL. dir
 
Miras / Kalıtım (INHERITANCE):

Konunun daha anlaşılır hale gelmesi için öncelikle bir örnek sınıf yazalım. Örnek şöyle olsun; taşeron işçiler ile çalışan bir firmanın muhasebesi için taşeronların cari hesaplarını kontrol eden bir uygulama yazalım...
C++:
#include "stdio.h"
class personel
{
    protected:
        const char *isim;
        int bakiye;
    
    public:
    personel(const char *isim, int bakiye=0)
    {
        this->isim = isim;
        this->bakiye = bakiye;
        printf("%d TL bakiye ile %s adlı personel için cari kaydı oluşturulmuştur.\n",this->bakiye,this->isim);
    }
    void odemeYap(int tutar)
    {
        if (tutar > this->bakiye)
        {
            printf("Talep edilen tutar (%d TL) için bakiye yetersiz!\n",tutar);
            return;
        }
        this->bakiye -=tutar;
        printf("Personel hesabından %d TL ödeme yapıldı.\n",tutar);
        bilgigoster();
    }
    void hakedis(int tutar)
    {
        this->bakiye +=tutar;
        printf("Personel cari hesabına %d TL eklendi.\n",tutar);
        bilgigoster();
    }
    void bilgigoster()
    {
        printf("%s Adlı Çalışanın Güncel cari hesap bakiyesi %d TL dir... \n",this->isim,this->bakiye);
    }
};

int main()
{
personel per1("Sıvacı Ahmet Usta");
per1.bilgigoster();
per1.hakedis(800);
per1.odemeYap(1000);
per1.odemeYap(800);
}
Program Çıktısı:
0 TL bakiye ile Sıvacı Ahmet Usta adlı personel için cari kaydı oluşturulmuştur.
Sıvacı Ahmet Usta Adlı Çalışanın Güncel cari hesap bakiyesi 0 TL dir... 
Personel cari hesabına 800 TL eklendi.
Sıvacı Ahmet Usta Adlı Çalışanın Güncel cari hesap bakiyesi 800 TL dir...
Talep edilen tutar (1000 TL) için bakiye yetersiz!
Personel hesabından 800 TL ödeme yapıldı.
Sıvacı Ahmet Usta Adlı Çalışanın Güncel cari hesap bakiyesi 0 TL dir...
.

Bu arada hepimizin anlama ve anlatma kabiliyeti farklı. Bir konuyu pekiştirmek için örnek ile anlatmak gerekir. Örneğin doğadaki bir konuyu örneklenmiş olabilir veya spor dalında bir örnek verilmiş olabilir. Doğa konulu örnekte konuyu anlayamazsın ama aynı prensipler ile çalışan spor konulu örneği görünce konu birden çözülür. İşte yukarıdaki örnekte benim OOP nin nasıl çalıştığını kavramaya başladığım benim için değerli bir örnektir. Kodlamanın orjinal pyton ile şu adresten alınmıştır:


Neyse örneğimize dönelim...
Bir firmaya yukarıdaki gibi bir uygulama yazdık veya bir başkası yazdı ve kodu bizimle paylaştı veya personel nesnesi hazır bir kütüphaneden elde ediliyor... Sonrasında bir başka firma aynı işi yapan bir uygulama talep etsin. Yalnız bir farkla... taşeronların hesaplarında para olmaza bile avans verilmesi isteniyor...

Şimdi taşeron adlı bir sınıf oluşturarak personel sınıfının üyelerine kalıtım/miras yolu ile sahip olacağız ve artık nesnelerimizi taşeron sınıfında üreteceğiz. Oluşan nesnelerimiz sanki personel sınıfından oluşmuş gibi aynı özelliklerin yanı sıra yeni özelliklere de sahip olacak...

Personel sınıfı yukarıda olduğunda tekrar yazmadan kodu ekliyorum;
C++:
class taseron:public personel
{
    protected:
    int avansBakiyesi;

    public:
    taseron(const char *isim, int bakiye=0):personel(isim,bakiye)
    {
       this->avansBakiyesi=0;
    }

    void avansVer(int tutar)
    {
        
        this->avansBakiyesi += tutar;
        printf("Personele %d TL avans verilmiştir. Toplam avans bakiyesi %d TL dir \n",tutar,this->avansBakiyesi);
        this->bakiye -= tutar;
        bilgigoster();
    }
};

int main()
{
taseron per1("Hasan Usta");
per1.odemeYap(500);
per1.avansVer(500);
per1.avansVer(300);
per1.hakedis(1000);

}
Program Çıktısı:
0 TL bakiye ile Hasan Usta adlı personel için cari kaydı oluşturulmuştur.
Talep edilen tutar (500 TL) için bakiye yetersiz!
Personele 500 TL avans verilmiştir. Toplam avans bakiyesi 500 TL dir
Hasan Usta Adlı Çalışanın Güncel cari hesap bakiyesi -500 TL dir...
Personele 300 TL avans verilmiştir. Toplam avans bakiyesi 800 TL dir
Hasan Usta Adlı Çalışanın Güncel cari hesap bakiyesi -800 TL dir...
Personel cari hesabına 1000 TL eklendi.
Hasan Usta Adlı Çalışanın Güncel cari hesap bakiyesi 200 TL dir...


Görüldüğü üzere taşeron sınıfı personel sınıfının tüm özelliklerine sahip oldu ve ayrıca ek özellikler eklendi. Örnek kabiliyet olarak nispeten basit. Teknikleri anlamak için nispeten zor... Birde şöyle düşünebilirsiniz. Binlerce satırdan oluşan hazır bir kütüphane zinciri var. Tek tek kopyala yapıştır veya değiştir yapamazsınız. Nihai sınıftan üretilen nesne işinizi görüyorsa sınıfı miras alarak istediğiniz eklemeleri yapar işi bitirirsiniz...

Şimdilik Kalıtım/Miras konusuna ufaktan bir giriş yaptık. Detaylar ile devam edeceğim...
 
INHERITANCE (Miras / Kalıtım) :

Bir sınıfın üyelerini kendisininmiş gibi kullanabilen yeni sınıfların türetilmesi konusudur. Miras yerine kalıtım kelimesini daha uygun buluyorum. Zira miras dendiği zaman miras bırakan artık elindekilere sahip değilmiş gibi bir anlam çıkartılabilir ki durum böyle değildir. Sınıflardan başka sınıflar türetildiğinde nitelikler kaybolmaz. Kalıtım kullanabilmek için öncelikle ortada niteliklerini kullanabileceğimiz bir sınıf olmalı. Bu sınıf, üst sınıf (base class/super class/parent class) ifadesi ile adlandırılırken türettiğimiz yeni sınıf(lar) ise alt sınıf (sub class/derived class) ifadesi ile anılır. Birden çok kaynakta geometrik şekillerin özelliklerine atfen konuya örnek verilmiş. Sanıyorum anlaşılır bir örnekleme ve bende goemetrik şekillerden devam edeceğim.

şekil adında bir sınıfımız olsun;
C++:
#include "stdio.h"
class sekil
{
    private:
        // private üyelere sınıf dışından erişilemez ve kalıtım yolu ile aktarılamaz.
    protected:
        // protected üyelere sınıf dışından erişilemez ancak kalıtım yolu ile aktarılır!
    public:
        // public alayına gider..
        // erişim belirteci etiketleri aynı sınıf içinde birden çok 
        // kullanılabilir.
    public:
        const char *sekilAdi;
        int alan;
    
    void bilgiVer()
    {
        printf("%s 'in alanı %d mm2 dir.",this->sekilAdi,this->alan);
    }
};

Şekil sınıfımızdan dikdörtgen adlı bir sınıf türetelim. dikdörtgen sınıfını tanımlarken sınıfın ismi verildikten sonra ":" işareti ile miras alınacak üst sınıfın adı yazılır.
C++:
// üyelerine erişilecek üst sıfın adı ":" işaretinden sonra yazılır.
class dikdortgen : public sekil
{
   //...
}

Dikkat ederseniz ":" işaretinden sonra miras alınan üst sınıfın adını yazmadan önce public ifadesini kullandık. Bu ifadenin amacı üst sınıftaki public erişim belirtecinden farklıdır.

public ile miras alma : üst sınıftaki public ve protected üyeler türetilmiş sınıftada aynı erişim denetimine tabi olurlar.
protected ile miras alma : üst sınıftaki public ve protected üyeler türetilmiş sınıfta protected üye olurlar.
private ile miras alma : üst sınıftaki public ve protected üyeler türetilmiş sınıfta private üye olurlar.

Örneği devam ettirerek tüm kodu yazalım;
C++:
#include "stdio.h"

class sekil
{
    public:
        const char *sekilAdi;
        int alan;

    void bilgiVer()
    {
        printf("%s 'in alanı %d mm2 dir.", sekilAdi, alan);
    }
};

class dikdortgen : public sekil
{
    public:
        int yukseklik;
        int genislik;
    
    dikdortgen() // yapıcı ile şekil adını atıyoruz.
    {
        sekilAdi= "Dikdötgen";
    }
    void alan_hesapla()
    {
        alan = genislik * yukseklik;
        bilgiVer();
    }
    
};


int main()
{
    dikdortgen s1;
    s1.genislik=10;
    s1.yukseklik=5;
    s1.alan_hesapla();
}
Program Çıktısı:
Dikdötgen 'in alanı 50 mm2 dir.


Bir sınıftan birden çok sınıf türetilebilir. örneğimize yine şekil sınıfından türeteceğimiz üçgen ve daire sınıflarını ekleyelim.

C++:
#include "stdio.h"

class sekil
{
    public:
        const char *sekilAdi;
        int alan;

    void bilgiVer()
    {
        printf("%s 'in alanı %d mm2 dir. \n", sekilAdi, alan);
    }
};

class dikdortgen : public sekil
{
    public:
        int yukseklik;
        int genislik;
    
    dikdortgen() // yapıcı ile şekil adını atıyoruz.
    {
        sekilAdi= "Dikdötgen";
    }
    void alan_hesapla()
    {
         alan = genislik * yukseklik;
         bilgiVer();
    }
    
};

class ucgen : public sekil
{
    public:
        int yukseklik;
        int taban;
    
    ucgen() // yapıcı ile şekil adını atıyoruz.
    {
        sekilAdi= "üçgen";
    }
    void alan_hesapla()
    {
        alan = taban * yukseklik/2;
        bilgiVer();
    }
    
};

class daire : public sekil
{
    public:
        int yaricap;
        
    
    daire() // yapıcı ile şekil adını atıyoruz.
    {
        sekilAdi= "Daire";
    }
    void alan_hesapla()
    {
        alan = yaricap * yaricap * 3; // pi 3 alınmıştır. :P
        bilgiVer();
    }
    
};
int main()
{
    dikdortgen s1;
    s1.genislik=10;
    s1.yukseklik=5;
    s1.alan_hesapla();

    ucgen s2;
    s2.yukseklik=10;
    s2.taban=5;
    s2.alan_hesapla();

    daire s3;
    s3.yaricap=5;
    s3.alan_hesapla();
}
Program Çıktısı:
Dikdötgen 'in alanı 50 mm2 dir.
üçgen 'in alanı 25 mm2 dir.
Daire 'in alanı 75 mm2 dir.
 
Kalıtım ile Yapıcı ve Yıkıcı Fonksiyonların İlişkisi :

A sınıfından B sınıfını türettiğimizi ve B sınıfından bir nesne oluşturduğumuzu düşünelim. B sınıfından oluşan nesne hem B sınıfının hem de A sınıfının özelliklerine sahip olacak. Bu durumda A sınıfından bir nesne üretmediğimiz halde arka planda sanki A sınıfından da bir nesne üretiliyormuş gibi B sınıfının yanında A sınıfının da yapıcı ve yıkıcı fonksiyonları çalışacaktır. Sıralama olarak; alt sınıftan bir nesne üretildiğinde önce üst sınıfın, sonra alt sınıfın yapıcı fonksiyonları arka arkaya çalışırlar. Yıkıcı fonksiyonlarda ise tersi olarak önce alt sınıfın sonra üst sınıfın yıkıcı fonksiyonu çalışır.

Örnek ile görelim;
C++:
#include "stdio.h"

class Sinif_A
{
    protected:
        int saat;
    public:
    Sinif_A()
    {
        printf(" A Sınıfının Yapıcısı Çalıştı... \n");
    }
    ~Sinif_A()
    {
        printf(" A Sınıfının Yıkıcısı Çalıştı... \n");
    }
};

class Sinif_B:public Sinif_A
{
    protected:
        int dakika;
    public:
    Sinif_B()
    {
        printf(" B Sınıfının Yapıcısı Çalıştı... \n");
    }
    ~Sinif_B()
    {
        printf(" B Sınıfının Yıkıcısı Çalıştı... \n");
    }
};

int main()
{
    // Yapıcılar çalışacak...
    Sinif_B Nesne1;

    printf("---------------------- \n");
    // program bittiği için yıkıcılar çalışacak...
}
Program Çıktısı:
 A Sınıfının Yapıcısı Çalıştı...
 B Sınıfının Yapıcısı Çalıştı...
----------------------
 B Sınıfının Yıkıcısı Çalıştı...
 A Sınıfının Yıkıcısı Çalıştı...


Peki A sınıfının parametre alan bir yapıcı fonksiyonu olsaydı ne olurdu...?

C++:
#include "stdio.h"

class Sinif_A
{
    protected:
        int saat;
    public:
    Sinif_A(int saat)
    {
        this->saat=saat;
        printf(" A Sınıfının Yapıcısı Çalıştı... \n");
    }
    ~Sinif_A()
    {
        printf(" A Sınıfının Yıkıcısı Çalıştı... \n");
    }
};

class Sinif_B:public Sinif_A
{
    protected:
        int dakika;
    public:
    Sinif_B()
    {
     
        printf(" B Sınıfının Yapıcısı Çalıştı... \n");
    }
    ~Sinif_B()
    {
        printf(" B Sınıfının Yıkıcısı Çalıştı... \n");
    }
};

int main()
{
    // Yapıcılar çalışacak...
    Sinif_B Nesne1;

    printf("---------------------- \n");
    // program bittiği için yıkıcılar çalışacak...
}
Program Çıktısı:
main.cpp:3:7: note: candidate: 'constexpr Sinif_A::Sinif_A(const Sinif_A&)'
 class Sinif_A
       ^~~~~~~
main.cpp:3:7: note:   candidate expects 1 argument, 0 provided

Görüldüğü üzere hata aldık. 1 argüman gerekli ama sen 0 sağladın olmaz bu iş diyor. Bu kodun çalışması için A sınıfının yapıcısı bir parametre almalı. peki şöyle yapsaydık olurmuydu..?
C++:
int main(){
    int zaman=3;
    Sinif_B Nesne2(zaman);
}

Böyle yaparsak B sınıfın yapıcı fonksiyonuna argüman göndermiş oluruz. B sınıfında parametre alan bir yapıcı olmadığında bu seferde başka bir hata alırız.
Çözüm şöyle olmalı;
C++:
class Sinif_B:public Sinif_A
{
    protected:
        int dakika;
    public:
    Sinif_B():Sinif_A(10)   // A sınıfı yapıcısına parametre veriyoruz...
    {
     
        printf(" B Sınıfının Yapıcısı Çalıştı... \n");
    }
...

"Constructor Delegation" yöntemi ile b sınıfının yapıcısı çalışmadan önce a sınıfının yapıcısına parametre gönderiyoruz. örnekte 10 değerini gönderdik. A sınıfının yapıcısı parametre almak zorunda idi ve artık bir parametreye sahip olduğu için programımız hatasız çalışacaktır.



Örneğimizi oluşturan sınıfların birden çok yapıcı yapıcı fonksiyonları olsaydı;

- türetilmiş sınıftan nesne oluşturulurken verilen parametreye göre B sınıfının ilgili yapıcısı çalışacaktır.
- B sınıfının çalışacak olan yapıcısı A sınıfının hangi yapıcı fonksiyonuna parametre gönderiyor ise A sınıfının ilgili yapıcısı çalışır.
 
Son düzenleme:
Veri Türlerinin Dönüşümleri (Type Conversion/Casting):

Bir veri türünün (değişken, yapı, nesne vs) başka bir veri türüne dönüştürülmesidir. Dönüşüm derleyici tarafından yapılıyor ise kapalı/örtülü tip dönüşüm (implicit type conversion), programcı tarafından ek bir talimat ile yapılıyor ise açık tip dönüşüm (explicit type conversion) olarak adlandırılır.

Biraz başa dönüp bu konu hakkında bazı detayları aktarmak/toparlamak istiyorum. Basit değişkenlerimizin hafızada kapladığı 1, 4, 8 Byte gibi alanlar vardı. Örneğin char (işaretsiz) türü 1Byte yer kaplamaktadır. 1Byte = 8 Bit olduğuna göre char türünün alabileceği maks değer (2^8)-1=255 dir. Gelin bu durumu tablo üzerinde görelim ;

1632563413877.png

Diğer tüler içinde hafızadan bitlerin yerleşimi aynı şekilde olur. Örneğin 4Byte olan int türünde 127 tam sayısı şu şekilde saklanır;

1632563779051.png


Hemen bu durumu örnekleyelim. char bir değeri int bir değişkene atayalım bu durumda derleyici otomatik olarak (kapalı tip) dönüşüm yapar.
C++:
#include "stdio.h"
int main()
{
    char val1=127;
    int sonuc;
    // Kapalı dönüşün yapılıyor.
    sonuc = val1; 
}


Aslında dönüşümün özü bu kadar basit. Bu örnek yukarı bir dönüşüm oldu. Büyük kabın içerisine küçük kabı koyduk. Gelin birde tersini yapalım, int türünü char türüne çevirelim;
C++:
#include "stdio.h"

int main()
{
    int val=220;

    unsigned char sonuc;
    // Kapalı dönüşün yapılıyor.
    sonuc = val;

    printf("Sonuc : %d",sonuc);
}
Program Çıktısı:
Sonuc : 220

Bu örnekte aşağı bir dönüşüm oldu. Benzetme ile büyük kaptaki sıvıyı küçük kaba koyduk. Ancak büyük kaptaki sıvı miktarı küçük kabın alacağı kadar az miktardaydı. Miktar fazla olursa ne olur? Taşma... Değişkenlerde de durum aynen böyledir. Kodla görelim;


C++:
#include "stdio.h"

int main()
{
    int val=300;

    unsigned char sonuc;
    // Kapalı dönüşün yapılıyor.
    sonuc = val;

    printf("Sonuc : %d",sonuc);
}
Program Çıktısı:
Sonuc : 44

Küçük kabımız 256 alıyor. Büyük kaptan küçük kabı doldurduk. Ancak büyük kapta hala sıvı var. Küçük kabı dışarıya (çöpe) boşalttık. Sonra kalan sıvıyı tekrar doldurduk. Küçük kapta ne kadar sıvı kaldı? İlk başta 300 vardı, 256 sını çöpe attık. 300-256=44 elimizde kalan oldu. Aşağı dönüşümlerde veri kaybı olabilir. Dikkat edilmesi gerekir.

Başka bir örnek yapalım. Derleyici aritmetiksel operatörlerde türler eşit değil ise küçük türü, büyük türe otomatik dönüştür.;
C++:
int main()
{
    int     val1 = 6;
    float   val2 = 1.25;

    // val1 oto floata dönüşür.. işlem 6.0/1.25 şeklindedir.
    float sonuc = val1 /val2;

    printf("Sonuc : %f",sonuc);
    // sonuc 4.8  olur...
}

Birde şunu yapalım int olan iki sayı ile sonucu küsüratlı bölme yapalım 3/2 gibi..
C++:
#include "stdio.h"

int main()
{
    int     val1 = 3;
    int     val2 = 2;

    float sonuc = val1 /val2;

    printf("Sonuc : %f",sonuc);
    // sonuc 1  olur...
}

sonucu float bir türe eşitlesekte iki int sayı bölümünde küsürat atıldığı için sonuç bir çıktı. işte bu aşamada bizim bir tür dönüşümü (açık tip) yapmamız gerekiyor. Görelim;

C++:
int main()
{
    int     val1 = 3;
    int     val2 = 2;

    // birini yapsakta olur diğeri oto. dönüşür.
    float sonuc = (float)val1 / (float)val2;
   
    printf("Sonuc : %f",sonuc);
    // sonuc 1.5  olur...
}

Görüldüğü üzere tür değiştirmek için değerin önüne parantez içinde istenen tür yazılarak gerçekleştirilir.


Pointer oluşturuken değişkenin türüne göre pointer oluşturuyorduk. Örneğin int türü bir pointer a char türü bir değişkenin adresini atayamayız. Ancak pointer tür dönüşünü ile bu münkün olur;
C++:
int main()
{
    char  val1;
 
    int *sonuc_ptr= (int*) &val1;
    // parantez içinde "*" var ise pointer tür dönüşümü söz konusudur.
 
}
Bu durumun en bilinen örneği C deki malloc fonksiyonudur.
C++:
int main()
{
   
    // int *rsv= malloc(4);  // hata verir.
    // doğru kullanım,
    int *rsv= (int*) malloc(4);
}

Bu aşamaya kadar bahsettiğimiz dönüşümler C dilinin dönüşümleridir. C++ ile yeni dönüşüm fonksiyonları eklenmiştir. Yeni fonksiyonlar ile dönüşüm yapmanın hata yakalamada daha avantajlı olduğu rivayet edilmektedir. Bu fonksiyonlar şunlardır:

- static_cast
- const_cast
- dynamic_cast
- reinterpret_cast

Static Cast :

C++:
int main()
{
   
    int val = 65;

    char sonuc;

    sonuc = static_cast<char>(val);

    printf("sonuc %d",sonuc);  
}

Const Cast:

Const bir türün const olmayan bir türe atanmasına izin verir;
C++:
int main()
{
   
    const char *metin = "Mekatronik";

    char *sonuc = const_cast<char*>(metin);

    printf("%s",sonuc);  
}

Diğer tür dönüşümleri yeri geldikçe örneklenecektir. Nesneler konusunu işlerken nesnelerinde de tür dönüşümü söz konusu olduğu için bu konudan bahsetme gereği oluştu. Araya sıkıştırdık...






Kaynaklar :
 
Son düzenleme:
Fonksiyon Ezme (function overriding)

Konular artık grift bir hal almaya başladı... Miras konusundan adım adım devam etmeye çalışalım. Temel bir sınıftan kalıtım alarak yeni bir sınıf türettik. Temel sınıfın değişken ve fonksiyonlarına sahibiz ancak miras alınan fonksiyonun çalışma şeklini değiştirmek istiyoruz. Bu durumda türetilmiş sınıfta ilgili fonksiyonu birebir aynı isim ve parametreler ile baştan yazıp fonksiyon gövdesini istediğimiz gibi yazıyoruz. Bu durumda miras yolu ile alınan temel sınıfın fonksiyonu yerine türetilmiş sınıfın fonksiyonu çalışır. Bu duruma fonksiyon ezme denir. Örnek ile görelim.
C++:
#include "stdio.h"

class sekil
{
    public:
        const char *sekilAdi;
        
    sekil()
    {
        sekilAdi="Geometrik Şekil";
    }
    void info()
    {
        printf("Temel Sınıf Fonksiyonu... \n Bu şekil bir %s 'dir. \n",sekilAdi);
    }
};

class daire : public sekil
{
    public:
        int yaricap;
    
    daire()
    {
        sekilAdi= "Daire";
        yaricap=15;
    }
    // fonksiyon yeniden yazılıyor. eskisi iptal...
    void info()
    {
        printf("Türetilmiş Sınıf Fonksiyonu... \n Bu şekil %d yarıcapına sahip bir %s dir. \n",yaricap,sekilAdi);
    }
  
};

int main()
{
// temel sınıf nesnesi
sekil s1;
// temel sınıf info fonksiyonu
s1.info();
// türetilmiş sınıf nesnesi
daire s2;
//türetilmiş sınıfta yeniden yazılan info fonksiyonu
s2.info();
}
Program Çıktısı:
Temel Sınıf Fonksiyonu...
 Bu şekil bir Geometrik Şekil 'dir.
Türetilmiş Sınıf Fonksiyonu...
 Bu şekil 15 yarıcapına sahip bir Daire dir.

Miras yolu ile aldığımız fonksiyonu istemiyorsak fonksiyonu baştan oluşturuyoruz. Basit bir konu...
 
Nesnelerde Tür Değişimi (Type Casting):

Şekil örneğinden devam edelim. Şekil sınıfından daire sınıfını türettik. kodumuzda hem şekil hemde daire türünde bir veri yapımız var. Sonra daire sınıfından bir nesne ürettiğimizi düşünelim. Herhangi bir ihtiyaçtan dolayı sonrana daire nesnemizi şekil sınıfından bir nesneye dönüştürmek isteyebiliriz. Bu durumda tür değişimi yapmak gerekir ve bu işlem sadece pointer kullanımı ile mümkün hale gelir. Örnek ile görelim.
C++:
#include "stdio.h"

class sekil
{
    public:
        const char *sekilAdi;
       
    sekil()
    {
        sekilAdi="Geometrik Şekil";
    }
    void info()
    {
        printf("Temel Sınıf Fonksiyonu... \n Bu şekil bir %s 'dir. \n",sekilAdi);
    }
};

class daire : public sekil
{
    public:
        int yaricap;
   
    daire()
    {
        sekilAdi= "Daire";
        yaricap=15;
    }
    // fonksiyon yeniden yazılıyor. eskisi iptal...
    void info()
    {
        printf("Türetilmiş Sınıf Fonksiyonu... \n Bu şekil %d yarıcapına sahip bir %s dir. \n",yaricap,sekilAdi);
    }
 
};

int main()
{
// türetilmiş sınıf nesnesi
daire s2;
// temel sınıftan pointer;
sekil *s1; // sekil *s1=(sekil*) &s2 böylede olur.
//tür dönüşü ile adres atama
s1= (sekil*) &s2;
// s1 artık sekil sınıfındanbir nesne ancak daire sınıfın özelliklerine sahip
s1->info();
}
Program Çıktısı:
Temel Sınıf Fonksiyonu...
 Bu şekil bir Daire 'dir.

Program çıktısından da anlaşılacağı üzere daire nesnemiz sekil nesnesine dönüştürüldükten sonra sadece sekil sıfından miras aldığı aldığı özellikleri koruyabiliyor. info fonksiyonu nu kaybettik. Çıktıda belli olmasada artık nesnemizin yarıcap değişkenide yok. Bu örnekte sadece şekil adını daire olarak koruyabildik...

Sanal Fonksiyonlar (Virtual Function) :

En son örneğimizden devam ediyoruz. tür değişimi sonrası info fonksiyonumuz kaybetmiştik. Bunu istemiyorsak işte bu durumda sanal fonksiyonlar devreye giriyor. Temel sınıftaki info fonksiyonunun başına virtual ifadesi ekleyerek aynı örneği bir daha çalıştırıp sonucu görelim.

Kod:
#include "stdio.h"

class sekil
{
    public:
        const char *sekilAdi;
        
    sekil()
    {
        sekilAdi="Geometrik Şekil";
    }
    virtual void info()
    {
        printf("Temel Sınıf Fonksiyonu... \n Bu şekil bir %s 'dir. \n",sekilAdi);
    }
};

class daire : public sekil
{
    public:
        int yaricap;
    
    daire()
    {
        sekilAdi= "Daire";
        yaricap=15;
    }
    // fonksiyon yeniden yazılıyor. eskisi iptal...
    void info()
    {
        printf("Türetilmiş Sınıf Fonksiyonu... \n Bu şekil %d yarıcapına sahip bir %s dir. \n",yaricap,sekilAdi);
    }
  
};

int main()
{
// türetilmiş sınıf nesnesi
daire s2;
// temel sınıftan pointer;
sekil *s1; // sekil *s1=(sekil*) &s2 böylede olur.
//tür dönüşü ile adres atama
s1= (sekil*) &s2;
// s1 artık sekil sınıfından bir nesne ancak daire sınıfın özelliklerine sahip
s1->info();
}
Program Çıktısı:
Türetilmiş Sınıf Fonksiyonu...
 Bu şekil 15 yarıcapına sahip bir Daire dir.

Görüldüğü üzere nesnemiz temel sınıf nesnesine dönüştürüldü ancak hala kendi (türetilmiş sınıf) fonksiyonunu koruyor. Bu durumu mümkün kılan sanal fonksiyondur.
 
Son düzenleme:
Nesne dizileri ( Örnek ) :

Şu ana kadar öğrendiğimiz özellikler ile basit bir örnek...
C++:
#include "stdio.h"

class sekil
{
    public:
        const char *sekilAdi;
        
    sekil()
    {
        sekilAdi="Geometrik Şekil";
    }
    virtual void info()
    {
        printf("Temel Sınıf Fonksiyonu... \n Bu şekil bir %s 'dir. \n",sekilAdi);
    }
};

class daire : public sekil
{
    public:
        int yaricap;
    
    daire()
    {
        sekilAdi= "Daire";
        yaricap=15;
    }
    // fonksiyon yeniden yazılıyor. eskisi iptal...
    void info()
    {
        printf("Türetilmiş %s Sınıfı Fonksiyonu... Bu şekil %d yarıcapına sahip bir %s dir. \n",sekilAdi, yaricap,sekilAdi);
    }
  
};

class ucgen : public sekil
{
    public:
        int yaricap;
        int yukseklik;
    ucgen()
    {
        sekilAdi= "Ucgen";
        yukseklik=33;
    }
    // fonksiyon yeniden yazılıyor. eskisi iptal...
    void info()
    {
        printf("Türetilmiş %s Sınıfı Fonksiyonu... Bu şekil %d yüksekliğe sahip bir %s dir. \n",sekilAdi,yukseklik,sekilAdi);
    }
  
};

class kare : public sekil
{
    public:
        int yaricap;
        int kenar;
    kare()
    {
        sekilAdi= "kare";
        kenar=12;
    }
    // fonksiyon yeniden yazılıyor. eskisi iptal...
    void info()
    {
        printf("Türetilmiş %s  Sınıfı Fonksiyonu... Bu şekil %d kenara sahip bir %s dir. \n",sekilAdi,kenar,sekilAdi);
    }
  
};

int main()
{
sekil *dizi[3];

ucgen s1;
daire s2;
kare *s3=new kare();

dizi[0]=&s1;
dizi[1]=&s2;
dizi[2]=s3;

for (sekil *eleman:dizi)
{
    eleman->info();
}


}
Program Çıktısı:
Türetilmiş Ucgen Sınıfı Fonksiyonu... Bu şekil 33 yüksekliğe sahip bir Ucgen dir.
Türetilmiş Daire Sınıfı Fonksiyonu... Bu şekil 15 yarıcapına sahip bir Daire dir.
Türetilmiş kare  Sınıfı Fonksiyonu... Bu şekil 12 kenara sahip bir kare dir.
 
Operatörlerin Aşırı yüklemesi (operator overloading):

ilk bakışta biraz karışık bir konu ama yine adım adım gitmeye çalışalım. Bu yöntem ile "+,-,/,<,... vb" operatörlere ek görevler tanımlanmaktadır.

Örnek üzerinden konuyu anlamak en sağlıklısı olacak kanısındayım. Direnc adında bir sınıf oluşturalım.
C++:
class direnc
{
    public:
        int r;
    
    direnc(int r=0)
    {
        this->r=r;
    }
    
    void degerGoster()
    {
        printf("Direnc degeri  %d Ohm dur. \n",r);
    }
};

sınıfımızda bir adet r değişkenimiz var. parametre alan yapıcımız var ve birde deger gösteren fonksiyonumuz var.
C++:
int main()
{
    direnc r1(10);
    r1.degerGoster();
}
Program Çıktısı:
Direnc degeri  10 Ohm dur.

main fonksiyonumuzda bir r1 nesnesi oluşturduk ve deger göster fonksiyonu ile değerini çıktı olarak aldık. peki şöyle yapmak isteseydik;
C++:
int main()
{
    direnc r1(10);
    direnc r2(47);
    direnc r3=r1+r2;
    r3.degerGoster();
}
Hata Mesajı:
bu işlenenlerle eşleşen "+" işleci yok -- işlenen türleri şunlar: direnc + direnc
derleyici bize doğrudan hata verecektir. Biz r1 ve r2 nin toplanmasını istiyoruz ancak derleyici bunu nasıl toplayacağını bilmiyor. Ancak biz illa istiyoruz :) Daha anlaşılır olması için gelin bu işlemi önce bildiğimiz yöntemler ile yapalım;
C++:
class direnc
{
    //...
    
    // dönüş tipi direnc (sınıf nesnesi), aldığı parametre direnc olan toplama fonksiyonu
    direnc topla(direnc R)
    {
        direnc rt; // direnç türünden bir geçici nesne
        rt.r = r + R.r;
        return rt;
    }
};
sınıfımızın içine bir toplama fonksiyonu yazalım. Toplam değer r3 nesnesine eşitleneceği için fonksiyonumuzun dönüş değeri direnc türünden olmalı. r3 ü şu şekilde yazar isek;
C++:
direnc r3=r2.topla(r3);
r3.degerGoster();
r2 nin topla fonksiyonunu çağırıyoruz dolayısı ile r2 nin değeri zaten elimizde mevcut. r3 değerine sahip olmak içinde r3 ü yine direnc türünden parametre olarak alıyoruz. nesnelerin r değerleri üzerinde toplama yaparak sonucu döndürüp r3 nesnesine atıyoruz. Sonuç olarak;
Program Çıktısı:
Direnc degeri  47 Ohm dur.
çıktısını alıyoruz. Şu ana kadar bildiğimiz yöntemler ile bu işi başardık... Bu aşamaya kadar olan işlemleri anladıysak aynı işlemi "+" operatörone ek görev tanılmayarak (over loading) gerçekleştirelim.

Aslında ek görevimiz "+" operatorü için bir fonksiyon yazmaktan ibaret olacak.
C++:
    // r2 nin topla fonksiyonuna r3 ü gönder
    direnc r3=r2.topla(r3);
    // r2 nin topla fonksiyonuna r3 ü gönder
    direnc r3=r2 + r3;
yani bizim + işaretimiz r2 için bir fonksiyon çağıracak parametresi de r3 olacak. işin özü bu... O halde bu fonksiyonumuzu yazalım. mevcut olan topla fonksiyonumuzu;
C++:
    direnc topla(direnc R)
    {
        direnc rt; // direnç türünden bir geçici nesne
        rt.r = r + R.r;
        return rt;
    }
aşağıdaki şekilde değiştirelim;
C++:
    direnc operator+(direnc R)
    {
        direnc rt; // direnç türünden bir geçici nesne
        rt.r = r + R.r;
        return rt;
    }
int main()
{
    direnc r1(10);
    direnc r2(47);
    direnc r3=r2 + r3;
    r3.degerGoster();
}
Program Çıktısı:
Direnc degeri  47 Ohm dur.
Hepsi bu kadar. Artık direnc türünden nesneleri + işareti ile doğrudan toplayabiliriz.

Konuyu biraz anlayıp kafamız hala karışık ise hadi birde paralel bağlama işlemi yapalım... Ondalıklar nedeni ile değişkenleri double türüne çevirdiğim için tüm kodu ekliyorum;
C++:
#include "stdio.h"
using namespace  std;

class direnc
{
    public:
        double r;
    
    direnc(double r=0)
    {
        this->r=r;
    }
    
    void degerGoster()
    {
        printf("Direnc degeri  %.2f Ohm dur. \n",r);
    }
    // dönüş tipi direnc (sınıf nesnesi), aldığı parametre direnc olan toplama fonksiyonu
    direnc operator+(direnc R)
    {
        direnc rt; // direnç türünden bir geçici nesne
        rt.r = this->r + R.r;
        return rt;
    }
    direnc operator|(direnc R)
    {
        direnc rt; // direnç türünden bir geçici nesne
        rt.r = (r * R.r)/(r + R.r);
        return rt;
    }
};

int main()
{
    direnc r1(10);
    direnc r2(47.0);

    direnc r3=r1 + r2;
    r3.degerGoster();
    
    direnc r4= r1|r2;
    r4.degerGoster();
}
Program Çıktısı:
Direnc degeri  57.00 Ohm dur.
Direnc degeri  8.25 Ohm dur.
"|" operatörü ile paralel diren hesabı yapan fonksiyonumuzu çağırmış olduk...
 
Operatörlerin Aşırı yüklemesi - II :

Önceki mesaj ile konuyu biraz anladıysak şimdi biraz daha derine inelim. Öncelikle yaptığımız örnekte sınıf içerisinden bir method ile operatör yüklemesi yaptık. Sınıftan bağımsız bir şekilde bu işlemi nasıl yapardık ona bakalım. Önce yine bildiğimiz yöntemler ile bu işi yapalım;
C++:
#include "stdio.h"
using namespace  std;

class direnc
{
    public:
        double r;
    
    direnc(double r=0)
    {
        this->r=r;
    }
    
    void degerGoster()
    {
        printf("Direnc degeri  %.2f Ohm dur. \n",r);
    }
};

direnc topla(direnc rA, direnc rB)
{
    direnc tmp;
    tmp.r = rA.r + rB.r;
    return tmp;
}


int main()
{
    direnc r1(10);
    direnc r2(47.0);
    direnc r3 = topla(r1,r2);
    
    r3.degerGoster();
}
Program Çıktısı:
Direnc degeri  57.00 Ohm dur.

Sınıftan bağımsız (dışarıda) bir fonksiyon kullandığımız için iki parametreli bir fonksiyon kullanmak durumunda kaldık. Şimdi bu fonksiyonu aşağıdaki şekilde değiştirerek "+" operatörünü yükleyelim;
C++:
direnc operator+(direnc rA, direnc rB)
{
    direnc tmp;
    tmp.r = rA.r + rB.r;
    return tmp;
}

Böylece topla(r1,r2) ifadesi yerine artık r1+r2 ifadesi kullanılabilir.





1632686269191.png


Operator aşırı yükleme fonksiyonları ile yukarıdaki operatörler kullanılabilir.
- Tekli operatörler : Tek değer ile işlem yapan operatörlerdir. arttır (++), azalt(--), değil(!) ,eksi(-) operatörleri tekli operatör gurubundadır.
Bu operatörler tek nesne üzerinde işlem yaparlar.

- İkili operatörler : İki değer ile işlem yapan operatörlerdir. topla(+), böl(/), çıkart (-) "eksi ile hem aynı hem farklı. aslında iki gruba dahil..." operatörleri bu gruba dahildir. İki nesne üzerinde işlem yapılır.

- Koşul operatörleri : iki değerin birbirine göre durumunu sorgulayan operatörlerdir.

Operatör aşırı yüklemesinde operatörlerin normaldeki amaçlarına benzer durumlar oluşturulur. Örneğin "!" operatörü ile yukarıdaki toplama işlemini yapamayız. Birden çok operatör aşırı yüklenerek kullanıldığında yine operatörlerin öncelik sırasına göre işlem yapılır.



Tekli operatör ile bir örnek daha yapalım ve konuyu kapatalım;
C++:
#include "stdio.h"

class direnc
{
    public:
        double r;
        const char *birim;
    
    direnc(double r=0)
    {
        this->r=r;
        birim="Ohm";
    }
    
    void degerGoster()
    {
        printf("Direnc degeri  %.0f %s dur. \n",r,birim);
    }

    void operator--(int)
    {
        r=r/1000;
        birim="KiloOhm";
    }
    void operator++(int)
    {
        r=r*1000;
        birim="MiliOhm";
    }
};



int main()
{
  direnc r1(1000);
  r1.degerGoster();
  r1--;  // birim değiştir.
  r1.degerGoster();
}
Program Çıktısı:
Direnc degeri  1000 Ohm dur.
Direnc degeri  1 KiloOhm dur.
 
Konuyu bu aşamada sonlandırmanın uygun olduğu kanısına vardım. Bu aşamaya kadar gelip devam etmek isteyenlere sınıflarda çok biçimlilik ve veri yığınları konularını araştırmalarını tavsiye ederim.
 
Explicit Constructor :

Doğrudan örnekle gidelim. Şöyle bir kodumuz olsun;

C++:
#include "stdio.h"

class sinif
{
    private:
        int val;
    
    public:
    sinif(int n)
    {
        val=n;
    }
};

int main()
{
    sinif n1='a';

    return 0;
}
Sınıfımızın yapıcı fonksiyonu int türünden bir parametre alıyor. Ancak main fonksiyonu içerisinde sınıftan n1 nesnesini oluştururken 'a' karakteri atanıyor ve kodumuz sorunsuz çalışıyor. Bu durumun sebebi tür dönüşümü.. 'a' karakteri otomatik olarak 97 sayısına (ascii) dönüşür.

Bu durum bizim için bir sorun oluşturuyor ve önlemek istiyorsak sınıfın içerisinde yapıcı fonksiyonun önüne "explicit" ifadesi eklenerek tür dönüşümü engellenir.

C++:
#include "stdio.h"

class sinif
{
    private:
        int val;
    
    public:
    explicit sinif(int n)
    {
        val=n;
        printf("%d",n);
    }
};

int main()
{
    sinif n1='a';   // hata verir.
    sinif n2(65);   // olması gereken...
    return 0;
}
 

Çevrimiçi personel

Forum istatistikleri

Konular
6,950
Mesajlar
118,747
Üyeler
2,822
Son üye
lalemasall

Son kaynaklar

Son profil mesajları

hakan8470 wrote on Dede's profile.
1717172721760.png
Dedecim bu gul mu karanfil mi? Gerci ne farkeder onu da anlamam. Gerci bunun anlamini da bilmem :gulus2:
Lyewor_ wrote on hakan8470's profile.
Takip edilmeye başlanmışım :D ❤️
Merhaba elektronik tutsakları...
Lyewor_ wrote on taydin's profile.
Merhabalar. Elektrik laboratuvarınız varsa bunun hakkında bir konunuz var mı acaba? Sizin laboratuvarınızı merak ettim de :)
Lyewor_ wrote on taydin's profile.
Merhabalar forumda yeniyim! Bir sorum olacaktı lcr meterler hakkında. Hem bobini ölçen hemde bobin direnci ölçen bir lcr meter var mı acaba?
Back
Top