nesneye yÖnelİk programlama dİnamİk bellek yÖnetİmİ

Post on 25-Jan-2016

59 Views

Category:

Documents

6 Downloads

Preview:

Click to see full reader

DESCRIPTION

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 - PowerPoint PPT Presentation

TRANSCRIPT

NESNEYE YÖNELİK PROGRAMLAMA

DİNAMİK BELLEK YÖNETİMİ

Özlem AYDIN

Trakya ÜniversitesiBilgisayar Mühendisliği BölümüNot: Bu sunumda Doç. Dr. Yılmaz KILIÇASLAN’ın Nesneye Yönelik Programlamadersi 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

• 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.

Referanslar

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; // Errordouble &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>using namespace std;void main(){ 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ı:

123123124124125125

Referanslar ve İşaretçiler

• Referansları örtük işaretçiler olarak düşünebiliriz:

int asil_int = 123;int *const intptr = &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çirme2. 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>using namespace std;void swap(int &i, int &j);int main(){int a, b, c, d;a = 1;b = 2;c = 3;d = 4;cout << "a ve b: " << a << " " << b << "\n";swap(a, b); // & operatoru kullanilmamaktacout << "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; // * operatoru kullanilmamakta i = j;j = t;}

Fonksiyon Parametresi Olarak Referanslar - 4

Program Çıktısı:

a ve b: 1 2a ve b: 2 1c ve d: 3 4c ve d: 4 3

Referansları Döndürme• Bir fonksiyon referans döndürebilir.

#include <iostream>using namespace std;int &f(); // başvuru döndürint x;int main(){f() = 100; // f() tarafından döndürülen başvuruya 100 atacout << 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>using namespace std;int main(){int x;int &ref=x; // bağımsız başvuru oluştur.x=10; // bu iki atama ref=10; // işlevsel olarak eşdeğerdirref=100;// bu 100 sayısını iki kere basarcout << 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’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 -- ÖrnekTarih *Ptr1, *Ptr2;int i;Ptr1 = new Tarih; //Varsayilan yap. fonk. cagriliri = Ptr1->aySoyle(); //1 (varsayilan deger) doner

Ptr2 = new Tarih(3,15,1985);//Yap. Fonk. cagriliri = 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 “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 atanircp = new char[uzunluk];// ...delete [] cp;

İşaretçi Elemanlı Sınıflar - 1• new ve delete operatö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>;

class String

{ public:

String();

String( const char *s );

String( char c, int n );

void belirle( int index, char yeniK );

char soyle( int index ) const;

int uzunlukSoyle() const { return uzunluk; }

void goruntule() const { cout << buf; }

~String();

private:

int uzunluk;

char *buf; };

İşaretçi Elemanlı Sınıflar - 2// Varsayilan Yapici FonksiyonString::String(){ buf = 0;

uzunluk = 0; }

// const char * alan Yapici FonksiyonString::String( const char * s ){ uzunluk = strlen( s );

buf = new char[uzunluk + 1]; strcpy( buf, s ); }

// char ve int alan Yapici FonksiyonString::String( char c, int n ){ uzunluk = n;

buf = new char[uzunluk + 1]; memset( buf, c, uzunluk ); buf[uzunluk] = ‘\0’; }

İşaretçi Elemanlı Sınıflar - 3// String içinde bir karakterin belirlenmesivoid String::belirle( int index, char yeniK){ if( (index > 0) && (index <= uzunluk) ) buf[index - 1] = yeniK ; }

// String içinden bir karakterin okunmasichar String::soyle( int index ) const{ if( (index > 0) && (index <= uzunluk) ) return buf[index - 1]; else

return 0; }

// Yikici FonksiyonString::~String(){ delete [] buf; }

İşaretçi Elemanlı Sınıflar - 4int main(){ 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:

void String::ekle( const char *ek ){ char *temp;

uzunluk += strlen( ek );temp = new char[uzunluk + 1];strcpy( temp, buf );strcat( temp, ek );delete [] buf;

buf = temp; }

int main(){ 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>

class String{ public:

String();String( const char *s );String( char c, int n );void operator=( const String &digeri );

// ... };void String::operator=(const String &digeri){ uzunluk = digeri.uzunluk; delete [] buf; buf = new char[uzunluk+1];

strcpy( buf, digeri.buf ); }

Atama operatörü - 2int main(){ 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:

1. 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ü - 42. 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 *this ifadesini 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( int mn ){ ay = mn; }

// ... tarih1.ayBelirle( 3 );

biçimindeki C++ kod parçasının C karşılığı şöyle olacaktır:

void ayBelirle(Tarih *const this, int mn ){ this->ay = mn; }

// ... ayBelirle( &tarih1, 3 );

“this” İşaretçisi - 4• this iş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:

void String::operator=(const String &digeri){ if( &digeri == this ) return; delete [] buf; uzunluk = digeri.uzunluk; buf = new char[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 3 olan 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=(const String &digeri){ if( &digeri == this ) return *this; delete [] buf; uzunluk = digeri.uzunluk; buf = new char[uzunluk+1];

strcpy( buf, digeri.buf ); return *this; }

40

“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 cout nesnesi olarak döndürmektedir.

41

top related