NESNEYE YÖNELİK PROGRAMLAMA NESNELERİN YARATILMASI VE YOK EDİLMESİ, FONKSİYONLARIN AŞIRI YÜKLENMESİ, CONST NESNELER, ELEMAN NESNELER, DİNAMİK BELLEK YÖNETİMİ Özlem AYDIN Trakya Üniversitesi Bilgisayar Mühendisliği Bölümü NESNELERİN YARATILMASI VE YOK EDİLMESİ - 1 Örnek: // DEMO.CPP #include <iostream> #include <cstring> using namespace std; class Demo { public: Demo(const char *nm); ~Demo(); private: char isim[20]; }; NESNELERİN YARATILMASI VE YOK EDİLMESİ - 2 Demo::Demo( const char *nm ) { strncpy( isim, nm, 20 ); cout << isim << “ icin yapici fonksiyon cagrimi\n”; } Demo::~Demo() { cout << isim << “ icin yikici fonksiyon cagrimi\n”; } void fonk() { Demo yerelFonkNesnesi(“yerelFonkNesnesi”); static Demo staticNesne(“statikNesne”); cout << ”fonk icinde” << endl; } NESNELERİN YARATILMASI VE YOK EDİLMESİ - 3 Demo globalNesne(“globalNesne”); int main() { Demo yerelMainNesnesi(“yerelMainNesnesi”); cout << “fonk cagrimindan once, main icinde\n”; fonk(); cout << “fonk cagrimindan sonra, main icinde\n”; } return 0; NESNELERİN YARATILMASI VE YOK EDİLMESİ - 4 Yerel nesneler için, yapıcı fonksiyon nesnenin bildirimi yapıldığında, yıkıcı fonksiyon ise nesne bildiriminin yapıldığı bloktan çıkarken çağrılır. Global nesneler için, yapıcı fonksiyon program başlarken, yıkıcı fonksiyon ise program sona ererken çağrılır. Statik nesneler için, yapıcı fonksiyon nesnenin bildiriminin yapıldığı fonksiyona ilk girişte, yıkıcı fonksiyon ise program sona ererken çağrılır. NESNELERİN YARATILMASI VE YOK EDİLMESİ - 5 Program çıktısı: globalNesne icin yerelMainNesnesi fonk cagrimindan yerelFonkNesnesi statikNesne icin fonk icinde yerelFonkNesnesi fonk cagrimindan yerelMainNesnesi statikNesne icin globalNesne icin yapici fonksiyon cagrimi icin yapici fonksiyon cagrimi once, main icinde icin yapici fonksiyon cagrimi yapici fonksiyon cagrimi icin yikici fonksiyon cagrimi sonra, main icinde icin yikici fonksiyon cagrimi yikici fonksiyon cagrimi yikici fonksiyon cagrimi ELEMAN SAHALARA ERİŞİM - 1 Şu anki haliyle, Tarih sınıfı gun, ay ve yil bileşenlerine erişim izni vermemektedir; örneğin, bir Tarih nesnesinin ay değerini okuyup değiştiremezsiniz. Bu sorunu gidermek için Tarih sınıfı aşağıdaki gibi değiştirilebilir: class Tarih { public: Tarih( int mn, int dy, int yr ); int aySoyle(); int gunSoyle(); int yilSoyle(); int ayBelirle( int mn ); int gunBelirle ( int dy ); int yilBelirle( int yr ); void goruntule(); ~Tarih(); private: int ay, gun, yil; }; ELEMAN SAHALARA ERİŞİM - 2 Erişim fonksiyonları şöyle tanımlanabilir: inline int Tarih::aySoyle() { return ay; } inline int Tarih::gunSoyle() { return gun; } inline int Tarih::yilSoyle() { return yil; } ELEMAN SAHALARA ERİŞİM - 3 void Tarih::ayBelirle( int mn ) { ay = max( 1, mn ); ay = min( ay, 12 ); } void Tarih::gunBelirle( int dy ) { static int uzunluk[]={0,31,28,31,30,31,3031,31,30,31,30,31}; gun = max( 1, dy ); gun = min( gun, uzunluk[ay] ); } void Tarih::yilBelirle( int yr ) { yil = max( 1, yr ); } ELEMAN SAHALARA ERİŞİM - 4 Aşağıdaki örnekte main fonksiyonu yeni tanımladığımız erişim fonksiyonlarını kullanmaktadır: int main() { int i; Tarih sonTarih( 3, 10, 2005 ); i = sonTarih.aySoyle(); sonTarih.ayBelirle( 4 ); sonTarih.ayBelirle(sonTarih.aySoyle()+1); return 0; } ELEMAN inline FONKSİYONLAR Soyle fonksiyonları çok kısa olmaları nedeniyle ve çağrımlarının “overhead” içermemesi nedeniyle inline olarak bildirildiler. Eleman fonksiyonların gövdesini sınıf bildirimi içine yerleştirmeniz halinde inline anahtar sözcüğünü kullanmadan inline bildirimlerini sağlamış olursunuz: class Tarih { public: Tarih( int mn, int dy, int yr ); int aySoyle() { return ay; } int gunSoyle() { return gun; } int yilSoyle() { return yil; } // ... }; YAPICI FONKSİYONLARIN AŞIRI YÜKLENMESİ - 1 Yeni eleman fonksiyonlarımızla, bir Tarih nesnesinin nasıl yaratılacağını yeniden belirleyebiliriz: class Tarih { public: Tarih(); Tarih( int mn, int dy, int yr ); // ... }; Tarih::Tarih() { ay = gun = yil = 1;} Tarih::Tarih( int mn, int dy, int yr ) { ayBelirle( mn ); gunBelirle( dy ); yilBelirle( yr ); } YAPICI FONKSİYONLARIN AŞIRI YÜKLENMESİ - 2 void main() { Tarih dogum1; Tarih dogum2( 12, 25, 1990 ); dogum1.ayBelirle( 3 ); dogum1.gunBelirle( 12 ); dogum1.yilBelirle( 1985 ); } const NESNELER VE ELEMAN FONKSİYONLAR-1 Değişkenleri olduğu gibi nesneleri de bildirebilirsiniz: const Tarih dogum1( 7, 4, 1776 ); const olarak Bu tür bildirimler nesnenin sabit olduğu ve dolayısıyla hiçbir eleman sahasının değiştirilemeyeceği anlamına gelir. Bir değişkeni const olarak bildirdiğinizde, derleyici bu değişkenin değerini değiştirebilecek işlemleri tespit edip uygun hata mesajlarını üretebilir. Ancak, derleyici bir eleman fonksiyonun nesnenin eleman sahalarını değiştirip değiştiremeyeceğini belirleyemez. Bu nedenle, hiçbir (sıradan) eleman fonksiyon sabit bir nesne için çağrılamaz. const NESNELER VE ELEMAN FONKSİYONLAR-2 Eğer bir eleman fonksiyon nesnenin hiçbir eleman sahasını değiştirmiyorsa const olarak bildirilebilir ve bu şekilde sabit nesneler için çağrılabilir. const anahtar sözcüğü sabit fonksiyonların hem bildiriminde hem de tanımlanmasında, parametre listesinden sonra yer alır. Sabit eleman fonksiyonlar ne nesnelerinin eleman sahalarını değiştirebilir, ne de sabit olmayan eleman fonksiyonları çağırabilirler. Eleman fonksiyonlarınızı mümkün olduğunca sabit olarak bildirmelisiniz. Bu, sınıfınızı kullananların sabit nesneler bildirmelerine izin verecektir. const NESNELER VE ELEMAN FONKSİYONLAR-3 class Tarih { public: Tarih(int mn, int dy, int yr); int aySoyle() const; int gunSoyle() const; int yilSoyle() const; int ayBelirle(int mn); int gunBelirle(int dy); int yilBelirle(int yr); void goruntule() const; ~Tarih(); private: int ay, gun, yil; }; inline int Tarih::aySoyle() const { return ay; } // ... int i; const Tarih dogum1( 7, 4, 1776 ); i = dogum1.yilSoyle(); // Legal dogum1.yilBelirle( 1492 );// Error ELEMAN NESNELER - 1 Bir sınıf, nesneleri eleman olarak içerebilir. Bu şekilde diğer sınıfları bileşen olarak kullanma suretiyle yeni bir sınıf tanımlama işlemine “bileşim (composition)” denir: class KisiBilgisi { public: // Public eleman fonksiyonlar... private: char isim[30]; char adres[60]; Tarih dogumTarihi; }; dogumTarih nesnesi KisiBilgisi sınıfından bir nesne yaratılıncaya kadar yaratılmaz. ELEMAN NESNELER - 2 Bir eleman nesne için yapıcı fonksiyon çağrımı, eleman nesneye ilk değer atamalarının yapılmasını gerektirir. Bunun icin yapılması gereken aşağıda örneklenmiştir: class KisiBilgisi { public: KisiBilgisi(char *nm, char *adr, int mn, int dy, int yr); // ... private: // ... }; ELEMAN NESNELER - 3 KisiBilgisi::KisiBilgisi(char *nm, char *adr,int mn, int dy, int yr) :dogumTarihi( mn, dy, yr ) { strncpy( isim, nm, 30 ); strncpy( adres, adr, 60 ); } İlk önce Tarih sınıfının yapıcı fonksiyonu çağrılır; böylelikle dogumTarihi nesnesi ilk değerlerini KisiBilgisi sınıfının yapıcı fonksiyonu çalıştırılmadan alır. Örnek #include <iostream> using namespace std; #define SIZE 100 // Stack sınıfı: class Stack { int stck[SIZE]; int tos; public: Stack(); // yapıcı (constructor) ~Stack(); // yıkıcı (destructor) void push(int i); int pop(); }; Örnek – devam… // Yapici Fonksiyon Stack::Stack() { tos = 0; cout << "Stack Initialized\n"; } // Yikici Fonksiyon Stack::~Stack() { cout << "Stack Destroyed\n"; } Örnek – devam… void Stack::push(int i) { if(tos==SIZE) { cout << "Stack is full.\n"; return; } stck[tos] = i; tos++; } Örnek – devam… int Stack::pop() { if(tos==0) { cout << "Stack underflow.\n"; return 0; } tos--; return stck[tos]; } Örnek – devam… int main() { Stack a, b; // iki stack nesnesi yaratildi a.push(1); b.push(2); a.push(3); b.push(4); cout << a.pop() cout << a.pop() cout << b.pop() cout << b.pop() return 0; } << << << << " "; " "; " "; "\n"; Örnek – devam… Program Çıktısı: Stack Initialized Stack Initialized 3 1 4 2 Stack Destroyed Stack Destroyed Dinamik Bellek Yönetimi – C dili C’de çalışma zamanında alınıp kullanılabilen bellek bölgesine “heap” denir. C’de heap’ten bellek istemek için malloc fonksiyonu kullanılır: struct t *t_ptr; t_ptr = (struct t *) malloc(sizeof(struct t)); malloc fonksiyonu ayrılmış belleğin başlangıcına bir işaretçi geri verir. Dinamik Bellek Yönetimi – C++ dili C++ “free store” olarak adlandırılan bir bellek bölgesinden dinamik olarak nesne yaratmak ya da yok etmek için bellek kullanımına izin verir. p-var=new type; delete p-var; type, bellekte yer ayrılacak nesnenin tipidir. p-var, o tipe olan işaretçidir. new operatörü, type ile tipi belirtilen nesneyi taşıyacak kadar genişliği olan, dinamik olarak ayrılmış belleğe bir işaretçi döndürür. Dinamik Bellek Yönetimi -- Örnek Tarih *Ptr1, *Ptr2; int i; Ptr1 = new Tarih; //Varsayilan yapıcı fonk. cagrilir i = Ptr1->aySoyle(); //1 (varsayilan deger) doner Ptr2 = new Tarih(3,15,1985); //Yapıcı Fonk. cagrilir i = Ptr2->aySoyle(); //3 doner Dinamik Bellek Yönetimi – new Derleyici new operatörünün döndürdüğü işaretçinin kendisi için bellek ayrılan nesne için olup olmadığını kontrol eder: void *Ptr; Ptr = new Tarih; // Tip uyumsuzluğu, hata! Nesne için ayrılan yerin döndürdüğü işaretçi nesne ile aynı tipte olmak zorundadır. Tarih *Ptr; Ptr = new Tarih; Dinamik Bellek Yönetimi - delete Nasıl malloc fonksiyonunun eşleniği olan bir free fonksiyonu varsa, new operatörünün de eşleniği olan bir delete operatörü vardır. delete operatörü ayrılan bellek bölgelerini daha sonra kullanılabilmek üzere “free store” bölgesine iade eder: Tarih *Ptr1; int i; Ptr1 = new Tarih(3, 15, 1985); i = Ptr1->aySoyle(); delete Ptr1; Dinamik Bellek Yönetimi - delete delete operatörü belleği geri vermeden önce otomatik olarak yıkıcı fonksiyonu çağırır. delete operatörünü yalnızca new ile döndürülen işaretçilere ve yalnızca bir kez uygulamalısınız. delete operatörünü, 0 değerli bir işaretçiye (“null pointer”) uygulayabilirsiniz. “Free store” ve diğer veri tipleri - 1 new ve delete operatörlerini yalnızca sınıflarla değil diğer derleyici ile birlikte gelen veri tipleri ile de kullanabilirsiniz: int *ip; ip = new int; //* ip = new int(3); // ... delete ip; * Dinamik olarak yer ayrılan bir nesneye ilk değer verebilirsiniz. “Free store” ve diğer veri tipleri - 2 new kullanarak bir boyutlu bir diziye dinamik olarak yer ayırabilirsiniz. Dinamik olarak ayrılmış diziyi silmek için delete [ ] kullanılır. Bu gösterim ile derleyicinin dizideki her elemanın yıkıcı fonksiyonunu çağırmasına neden olur. int uzunluk; char *cp; // uzunluk degiskenine bir deger atanir cp = new char[uzunluk]; // ... delete [] cp; Kaynaklar Prof. Dr. Yılmaz Kılıçaslan, Nesneye Yönelik Programlama dersi sunumları