410 likes | 807 Views
NESNEYE YÖNELİK PROGRAMLAMA DİNAMİK BELLEK YÖNETİMİ. Özlem AYDIN Trakya Üniversitesi Bilgisayar Mühendisliği Bölümü. Not: Bu sunumda Doç. Dr. Yılmaz KILIÇASLAN’ın Nesneye Yönelik Programlama dersi sunumlarından faydalanılmıştır. Sunum planı. Referanslar New ve delete operatörleri
E N D
NESNEYE YÖNELİK PROGRAMLAMADİNAMİK BELLEK YÖNETİMİ Özlem AYDIN Trakya Üniversitesi Bilgisayar Mühendisliği Bölümü Not: Bu sunumda Doç. Dr. Yılmaz KILIÇASLAN’ın Nesneye Yönelik Programlama dersi sunumlarından faydalanılmıştır.
Sunum planı • Referanslar • New ve delete operatörleri • İşaretçi eleman sınıflar • Atama operatörü • this işaretçisi
Referanslar • Referans (başvuru), bir değişkenin diğer bir ismi (alias) olarak davranan bir işaretçidir. Referanslar üç şekilde kullanılabilir: • Referanslar bir fonksiyona geçirilebilir. • Bir fonksiyon tarafından döndürülebilir. • Bağımsız bir referans oluşturulabilir. • Referansların en önemli kullanımı bir fonksiyona parametre olarak geçirilmesidir.
Alternatif Değişken İsimleri Olarak Referanslar • Referansları değişkenler için alternatif isimler (alias) olarak düşünebilirsiniz. • Bir referansa ilk değer atanırken, referans bir değişkenle ilişkilendirilir ve hep bu şekilde kalır: int asil_int; int &diger_int = asil_int; //Referans bildirimi
Alternatif Değişken İsimleri Olarak Referanslar • Bir referans ilk değer verilerek tanımlanmak zorundadır. int &r; // Error double &r=10.2 // Error • Referansa verilen ilk değer aynı türden bir nesne olmak zorundadır. double x=10; int &r=x; /* Error. Farklı türden bir nesneye ilk değer verilmiş. */
Örnek - 1 #include <iostream> usingnamespacestd; voidmain() { int asil_int = 123; int &diger_int = asil_int; cout << "\n" << asil_int; cout << "\n" << diger_int; diger_int++; cout << "\n" << asil_int; cout << "\n" << diger_int; asil_int++; cout << "\n" << asil_int; cout << "\n" << diger_int;} return 0; }
. . . Program Çıktısı: 123 123 124 124 125 125
Referanslar ve İşaretçiler • Referansları örtük işaretçiler olarak düşünebiliriz: int asil_int = 123; int *constintptr = &asil_int; // Sabit işaretçi • Fakat, bir işaretçi ile işaret ettiği değişkeni * içerik operatörünü (indirection) kullanarak ayırabilirken bunu referanslar için yapamayız. Dolayısıyla, referanslar üzerinde aşağıdaki işlemleri yapmak olanaksızdır: - Referansa işaret etmek - Referansın adresini almak - Birden fazla referansı karşılaştırmak - Referansa yeni bir değer atamak - Referanslar üzerinde aritmetik işlem yapmak
Fonksiyon Parametresi Olarak Referanslar - 1 • C’de bir fonksiyona parametre geçirmenin iki yolu vardır: 1. Değer ile (değişkenin kendisini) geçirme 2. Adres ile (değişkenin işaretçisini) geçirme • C++’da bir üçüncü seçenek mevcuttur: 3. Değişkenin referansını geçirme
Fonksiyon Parametresi Olarak Referanslar - 2 #include <iostream> usingnamespacestd; voidswap(int &i, int &j); intmain() { int a, b, c, d; a = 1; b = 2; c = 3; d = 4; cout << "a ve b: " << a << " " << b << "\n"; swap(a, b); // & operatorukullanilmamakta cout << "a ve b: " << a << " " << b << "\n"; cout << "c ve d: " << c << " " << d << "\n"; swap(c, d); cout << "c ve d: " << c << " " << d << "\n"; return 0; }
Fonksiyon Parametresi Olarak Referanslar - 3 void swap(int &i, int &j) { int t; t = i; // * operatorukullanilmamakta i = j; j = t; }
Fonksiyon Parametresi Olarak Referanslar - 4 Program Çıktısı: a ve b: 1 2 a ve b: 2 1 c ve d: 3 4 c ve d: 4 3
Referansları Döndürme • Bir fonksiyon referans döndürebilir. #include <iostream> usingnamespacestd; int&f(); // başvuru döndür int x; intmain() { f() = 100; // f() tarafından döndürülen başvuruya 100 ata cout << x << “\n”; return 0; } int &f() { return x; // x’e başvuru döndür. } Burada f() fonksiyonu x değişkenine bir başvuru döndürmektedir.
Bağımsız referans • Bağımsız referans başka bir değişken için farklı isim veren bir referans değişkenidir. • Pek kullanılmazlar. #include <iostream> usingnamespacestd; intmain() { int x; int &ref=x; // bağımsız başvuru oluştur. x=10; // bu iki atama ref=10; // işlevsel olarak eşdeğerdir ref=100; // bu 100 sayısını iki kere basar cout << x << “ “ << ref << “\n”; return 0 ; Burada ref adındaki bağımsız başvuru x için farklı bir isim sunar. x ve ref aslında eşdeğerdir.
Dinamik Bellek Yönetimi – C dili • C’de çalışma zamanında alınıp kullanılabilen bellek bölgesine “heap” denir. • C’deheap’ten bellek istemek için mallocfonksiyonu kullanılır: struct t *t_ptr; t_ptr = (struct t *) malloc(sizeof(struct t)); • mallocfonksiyonu ayrılmış belleğin başlangıcına bir işaretçi geri verir.
Dinamik Bellek Yönetimi – C++ dili • C++ “freestore” 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=newtype; 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. fonk. cagrilir i = Ptr1->aySoyle(); //1 (varsayilandeger) doner Ptr2 = new Tarih(3,15,1985);//Yap. 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
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 “freestore” 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 (“nullpointer”) uygulayabilirsiniz.
“Freestore” 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 = newint; //* ip = newint(3); // ... delete ip; * Dinamik olarak yer ayrılan bir nesneye ilk değer verebilirsiniz.
“Freestore” 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 degeratanir cp = newchar[uzunluk]; // ... delete [] cp;
İşaretçi Elemanlı Sınıflar - 1 • new ve deleteoperatörlerini bir sınıfın eleman fonksiyonları içinde kullanabilirsiniz. • Örnek: Her nesnesinin bir karakter katarı içereceği bir String sınıfı. #include <iostream>; #include <cstring>; classString { public: String(); String( constchar *s ); String( char c, int n ); void belirle( intindex, charyeniK ); charsoyle( intindex ) const; intuzunlukSoyle() const { return uzunluk; } voidgoruntule() const { cout << buf; } ~String(); private: int uzunluk; char *buf; };
İşaretçi Elemanlı Sınıflar - 2 // VarsayilanYapici Fonksiyon String::String() { buf = 0; uzunluk = 0; } // constchar * alan Yapici Fonksiyon String::String( constchar * s ) { uzunluk = strlen( s ); buf = newchar[uzunluk + 1]; strcpy( buf, s ); } // char ve int alan Yapici Fonksiyon String::String( char c, int n ) { uzunluk = n; buf = newchar[uzunluk + 1]; memset( buf, c, uzunluk ); buf[uzunluk] = ‘\0’; }
İşaretçi Elemanlı Sınıflar - 3 // String içinde bir karakterin belirlenmesi voidString::belirle( intindex, charyeniK) { if( (index > 0) && (index <= uzunluk) ) buf[index - 1] = yeniK ; } // String içinden bir karakterin okunmasi charString::soyle( intindex ) const { if( (index > 0) && (index <= uzunluk) ) returnbuf[index - 1]; else return 0; } // Yikici Fonksiyon String::~String() { delete [] buf; }
İşaretçi Elemanlı Sınıflar - 4 intmain() { String string1(“birinci Karakter Katari”); string1.belirle(1, ‘B’); return 0; }
İşaretçi Elemanlı Sınıflar - 5 • Her String nesnesi, birisi uzunluk ve buf eleman sahalarını içeren ve diğeri karakterlerin kendilerini depolayan iki bloktan oluşur. Nesnelerin içeriğini dinamik olarak değiştirmek mümkündür: voidString::ekle( constchar *ek ) { char *temp; uzunluk += strlen( ek ); temp = newchar[uzunluk + 1]; strcpy( temp, buf ); strcat( temp, ek ); delete [] buf; buf = temp; } intmain() { String string1(“birinci karakter katari”); string1.ekle(“ ve devami”); return 0; }
İşaretçi Elemanlı Sınıflar - 6 • Bir String nesnesi kapsam alanının dışına çıkınca, uzunluk ve buf değerlerinin içeren bellek bloğu otomatik olarak serbest bırakılır. • Fakat, karakter bloğu new ile alındığı için, açıkça serbest bırakılmalıdır. delete operatörünü kullanan yıkıcı fonksiyon bu işlevi yerine getirir.
İşaretçi Elemanlı Sınıflar - 7 • Yine de String sınıfı bazı problemlere potansiyel olarak açıktır. main fonksiyonuna aşağıdaki kod parçasını eklediğimizi varsayalım: String string2(“ikinci karakter katari”); string2 = string1; • Bir nesnenin bir diğerine atanmasını istediğimizde derleyici eleman bazında atama yapar. Yani, yukarıdaki atama deyimi aşağıdaki koda denktir: string2.uzunluk = string1.uzunluk; string2.buf = string1.buf;
İşaretçi Elemanlı Sınıflar - 8 • Bir String nesnesini diğerine doğrudan atamamız neticesinde karşılaşacağımız problemler şunlardır: • String nesnelerinden herhangi birine yapılacak bir değişiklik diğerini de etkileyecektir; bu muhtemelen arzu edilir bir durum olmayacaktır. • string1 ve string2 nesnelerinin buf işaretçisi yıkıcı fonksiyonları üzerinden ayrı ayrı silinecektir. İşaretçiler aynı değeri taşıdığı için bu tahmin edilemeyen sorunlara yol açabilir. • “ikinci karakter katari”değerini taşıyan string2 nesnesinin orijinal ‘buffer’ bloğu serbest bırakılmadan kaybolmuştur.
Atama operatörü - 1 • Nasıl fonksiyon isimleri aşırı yüklenebiliyorsa (gerçekte birer fonksiyon olan) operatör isimleri de aşırı yüklenebilir. Aşağıda atama operatörünün aşırı yüklenmesini görmekteyiz: #include <iostream> #include <cstring> classString { public: String(); String( constchar *s ); String( char c, int n ); voidoperator=( constString &digeri ); // ... }; voidString::operator=(constString &digeri) { uzunluk = digeri.uzunluk; delete [] buf; buf = newchar[uzunluk+1]; strcpy( buf, digeri.buf ); }
Atama operatörü - 2 intmain() { String string1(“birinci karakter katari” ); string1.goruntule(); cout << ‘\n’; String string2(“ikinci karakter katari” ); string2.goruntule(); cout << ‘\n’; string2 = string1; string2.goruntule(); cout << ‘\n’; return 0; } Derleyici string2 = string1; deyimini aşağıdaki fonksiyon çağrımı gibi yorumlayacaktır: string2.operator=( string1 );
Atama operatörü - 3 • operator=() fonksiyonunun iki önemli özelliği vardır: • Fonksiyon bir referans parametresi alır. Böylece atamanın sağ tarafındaki nesnenin kopyasının yapılması engellenir. Şöyle ki fonksiyonlara gönderilen nesnenin kopyası yapılır ve fonksiyon sona erdiğinde bu kopya yok edilir. Bu durumda kopya yok edilseydi yıkıcı fonksiyon çağırılacaktı ve işaretçi serbest kalacaktı. Fakat bu işaretçi nesne tarafından hala gerek duyulan işaretçidir. Referans kullanılması ile bu problem çözülmüştür.
Atama operatörü - 4 • operator=() fonksiyonunun ikinci en önemli özelliği nesne yerine referans döndürmesidir. Fonksiyon nesne döndürüyorsa geçici bir nesne oluşturulur ve bu nesne döndürme işleminin sona ermesiyle yok edilir. Ancak bu durumda geçici nesnenin yıkıcısı çağrılır ve bu fonksiyon işaretçiyi serbest bırakır. Fakat bu işaretçi değer atanmakta olan nesne için gereklidir. Sonuç olarak referans döndürülürse geçici nesnenin oluşturulmasını ve işaretçinin serbest bırakılmasını engelleriz.
“this” İşaretçisi - 1 • this işaretçisi statik eleman fonksiyonlar haricindeki eleman fonksiyonların erişebildiği özel bir işaretçidir. Eleman fonksiyonu çağıran nesneye işaret eder. • Daha açıkçası bir eleman fonksiyonu bir nesne için çağırdığınız zaman, derleyici önce bu nesnenin adresini this işaretçisine atar ve ardından fonksiyonu çağırır.
“this” İşaretçisi - 2 • Bir eleman fonksiyonu yazarken herhangi bir eleman sahaya erişmek için this işaretçisini açıkça kullanmak ya da fonksiyonu çağıran nesneye referansta bulunmak için *thisifadesini kullanmak meşru yöntemlerdir. Aşağıdaki örnekteki üç deyim birbirine denktir: void Tarih::ay_goruntule() { cout << ay; cout << this->ay; cout << (*this).ay; }
“this” İşaretçisi – 3 • Eleman fonksiyon sınıfın eleman sahalarına her erişiminde örtük olarak this işaretçisini kullanır. Örneğin, void Tarih::ayBelirle( intmn ) { ay = mn; } // ... tarih1.ayBelirle( 3 ); biçimindeki C++ kod parçasının C karşılığı şöyle olacaktır: voidayBelirle(Tarih *constthis, intmn ) { this->ay = mn; } // ... ayBelirle( &tarih1, 3 );
“this” İşaretçisi - 4 • thisişaretçisi bir eleman fonksiyonu çağıran nesne ile bu fonksiyona parametre olarak gönderilen nesnenin aynı nesneler olup olmadığını anlamak için kullanılabilir ve bu şekilde örneğin nesneyi kendisine değer olarak atama probleminden kaçınılabilir: voidString::operator=(constString &digeri) { if( &digeri == this ) return; delete [] buf; uzunluk = digeri.uzunluk; buf = newchar[uzunluk+1]; strcpy( buf, digeri.buf ); }
“this” İşaretçisi - 5 • Hem C’de hem C++’da bir atama deyimi atananı değer olarak alan bir ifade gibi düşünülebilir. Örneğin, i = 3; değeri 3olan bir ifadedir. • Bu durumun bir neticesi birden fazla atama deyiminin zincirleme olarak birbirine bağlanabilmesidir: a = b = c; • Atama operatörü sağdan birleştirmeli olduğundan yukarıdaki ifade aşağıdakiyle denktir: a = (b = c);
“this” İşaretçisi - 6 • Bu zincirleme atama özelliğini aşırı yüklenmiş nesne atama fonksiyonunuzun da kullanmasını istiyorsanız, fonksiyonun atama sonucunu değer olarak döndürmesini sağlamalısınız: String &String::operator=(constString &digeri) { if( &digeri == this ) return *this; delete [] buf; uzunluk = digeri.uzunluk; buf = newchar[uzunluk+1]; strcpy( buf, digeri.buf ); return *this; }
“this” İşaretçisi - 7 • Artık cout ifadelerinin de nasıl birden fazla çıktı değeri aldığını açıklayabiliriz: cout << a << b << c; Aşırı yüklenmiş sola kaydırma operatörü *this değerini coutnesnesi olarak döndürmektedir.