NESNEYE YÖNELİK PROGRAMLAMA DİNAMİK BELLEK YÖNETİMİ

advertisement
NESNEYE YÖNELİK PROGRAMLAMA
THIS İŞARETÇİSİ,
KOPYA YAPICI FONKSİYON,
STATİK ELEMANLAR,
ARKADAŞ SINIF VE FONKSİYONLAR,
NESNE DİZİLERİ
Özlem AYDIN
Trakya Üniversitesi
Bilgisayar Mühendisliği Bölümü
Sunum Planı

this işaretçisi

Kopya yapıcı fonksiyonu

Nesnelerin parametre olarak geçirilmesi ve döndürülmesi

Referansların parametre olarak geçirilmesi ve döndürülmesi

Statik veri elemanları ve statik fonksiyonlar

Arkadaş sınıflar ve fonksiyonlar

Nesne dizileri yaratma
“this” İşaretçisi - 1
•
Belleğin etkin kullanımı için sınıflarda tanımlı eleman
fonksiyonlar ilgili sınıftan yaratılan her nesne için tekrar
kopyalanmaz. Eleman fonksiyonun tek kopyasını o sınıfın tüm
nesneleri paylaşır. Fonksiyonun hangi nesne için çağrıldığı this
işaretçisi tarafından belirlenir.
•
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
#include <iostream>
using namespace std;
class Test {
int deger1;
public:
Test(int x) { deger1=x }
void yazdır();
};
void Test::yazdır()
{
cout << “Değer1= ” << deger1;
//cout << “Değer1= ” << this->deger1;
}
int main(){
Test t1(1), t2(2);
t1.yazdır();
t2.yazdır();
return 0;
}
t1 nesnesi
this
deger1
1
t2 nesnesi
this
deger1
2
“this” İşaretçisi - 3
•
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 – 4
•
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 )
// ...
tarih1.ayBelirle( 3 );
{
ay = mn;
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 - 5
•
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 - 6

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 - 7

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;
}
9
“this” İşaretçisi - 8

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.
10
ATAMA VE İLK DEĞER ALMA - 1

Atama mevcut bir nesnenin değerinin değişmesi
esnasında gerçekleşir; bir nesneye birçok yeni
değer atanabilir.

İlk değer alma bir nesnenin bildirimi yapılırken bir
başlangıç değeri alması esnasında gerçekleşir; bir
nesne yalnızca bir kez ilk değer alır.
ATAMA VE İLK DEĞER ALMA - 2

Atama ve ilk değer alma işlemleri arasındaki fark, nesnelerle
işlem yaparken önemli olabilir. Atama deyimi derleyicinin sınıf
için tanımlanmış operator= fonksiyonunu çalıştırmasına neden
olurken:
String string1(“birinci karakter katari”);
String string2;
string2 = string1;
ilk değer alma aynı fonksiyonu çalıştırmaz:
String string1(“birinci karakter katari”);
String string2 = string1;
operator= fonksiyonu yalnızca daha önce yaratılmış nesneler
için çağrılabilir.
KOPYA YAPICI FONKSİYON - 1

Bir nesnenin yaratıldığı esnada bir başka nesnenin
değerini almasını sağlamak için özel bir yapıcı fonksiyon
olan kopya yapıcı fonksiyon çağrılır.
Kopya yapıcı fonksiyon var olan bir nesnedeki verileri
yeni oluşturulan nesnenin içine kopyalar.
 Giriş parametresi olarak aynı sınıftan bir nesneyi referans
alırlar.
 Eğer programda bir kopya yapıcı fonksiyon tanımlı
değilse, derleyici standart bir yapıcıyı sınıfa yerleştirir.
Standart kopya yapıcı fonksiyon bir nesnenin
elemanlarını bire bir yeni nesnenin veri alanlarına
kopyalar. Bu da içinde işaretçi barındırmayan nesneler
için yeterlidir.

KOPYA YAPICI FONKSİYON - 2

Kopya yapıcı bir fonksiyon bir nesnenin ilk değer olarak
bir başka nesneyi aldığı durumlarda çalıştırılır. = işareti ile
ya da fonksiyon çağırma sentaksı ile çağrılır:
String string2 = string1;
String string2(string1);

Bir kopya yapıcı fonksiyon olmaması halinde yukarıdaki
ifade neticesinde string2 her eleman sahasının ilk
değerini string1’in elemanlarından alır. Bu da sınıfın
elemanları içinde işaretçilerin bulunması durumunda
arzu edilmeyen sonuçlara yol açabilir.
KOPYA YAPICI FONKSİYON - 3

String sınıfı için kopya yapıcı fonksiyonu şu şekilde yazılabilir:
#include <iostream>
#include <cstring>
using namespace std;
class String
{ public:
String();
String( const char *s );
String( char c, int n );
String( const String &digeri ); // kopya yapıcı fonksiyon
// ... };
String::String( const String &digeri )
{
uzunluk = digeri.uzunluk;
buf = new char[uzunluk + 1];
strcpy( buf, digeri.buf );
}
KOPYA YAPICI FONKSİYON - 4
Kopya yapıcı fonksiyonu derleyici tarafından 3
durumda çağırılır:

1. Bir sınıf nesnesi fonksiyona parametre olarak
geçirildiğinde.
2. Bir fonksiyondan bir nesne döndürüldüğünde.
3. Var olan bir nesnenin kopyası olarak yeni bir
nesneye başlangıç ataması yapıldığında .
NESNELERİN PARAMETRE OLARAK GEÇİRİLMESİ

Aşağıdaki fonksiyon bir nesneyi parametre olarak almaktadır:
void tuket(String parm)
{
// parm nesnesinin kullanımı
}
void main()
{ String string1(“birinci karakter katari”);
tuket(string1); }
tuket fonksiyonu değer olarak geçirilmiş bir String nesnesi almıştır. Yani, fonksiyon
nesnenin kendisine ait bir kopyasına sahip olmuştur. Aslında, derleyici örtük olarak
fonksiyonun parametresine nesneyi ilk değer olarak atamak üzere, kopya yapıcı
fonksiyonu çağırmıştır; yaptığı aşağıdakiyle aynıdır:
String parm(string1);
String parm = string1;
NESNE DÖNDÜREN FONKSİYONLAR

Aşağıdaki fonksiyon değer olarak bir nesne döndürmektedir:
String f()
{ String retDeger(“bir return değeri”);
return retDeger;
}
void main()
{ String string2;
string2 = f(); }
f fonksiyonu değer olarak bir String nesnesi döndürmektedir.
Derleyici, fonksiyonu çağıranın kapsam alanındaki geçici bir gizli
nesneye fonksiyonun return ifadesinde belirtilmiş nesneyi kullanarak
ilk değer atamak üzere kopya yapıcı fonksiyonu çağırır. Yani,
derleyici aşağıdakilere denk işlemler gerçekleştirir:
String gecici(retDeger);
string2 = gecici;
NESNE REFERANSLARININ PARAMETRE OLARAK GEÇİRİLMESİ

Bir nesnenin değer olarak bir fonksiyona her gönderilişi, bir miktar
“overhead” içerir. Sabit bir nesneye referans geçirerek aynı etki, yapıcı
fonksiyon çağırmaktan kaynaklı maliyetten de kurtularak elde edilebilir:
void tuket(const String &parm){ //parmnesnesinin kullanımı
void main()
{ String string1(“birinci karakter katari”);
tuket(string1); }

Parametre bu şekilde geçirildiğinde, yeni bir nesne yaratılmadığı için,
kopya yapıcı fonksiyon çağrılmaz. Derleyici aşağıdaki işlemin aynısını
gerçekleştirir:
const String &parm = string1;

}
Sonuç olarak, fonksiyon kendisini çağıranla aynı nesneyi kullanmış olur.
NESNE REFERANSLARININ DEĞER OLARAK DÖNDÜRÜLMESİ

Nesnenin referansını değer olarak döndürmek de, nesnenin kendisini
döndürmekten daha etkin bir yöntem olabilir. operator= örneğini hatırlayın:
String &String::operator=(const String &digeri)
{ //...
return *this; }
void main()
{ String string1(“birinci karakter katari”);
String string2, string3;
string3 = string2 = string1; }

Geçici bir nesne yaratılmadığı için fonksiyon değer döndürürken kopya yapıcı fonksiyon
çağrılmaz, yalnızca geçici bir referans yaratılır.
string2 = string1 ifadesinin değerini string3 nesnesine atarken derleyici aşağıdaki
işlemleri yapar:
String &geciciRef = string2;
string3 = geciciRef;
STATİK VERİ ELEMANLARI - 1

Bir veri elemanının
mümkündür.
static
olarak
tanımlanması

Bir sınıftan üretilen tüm nesnelerin ortak bir veriyi
paylaşmasının gerektiği durumlar olabilir.

Bellekte sadece tek kopyasının yaratılması istenen
veriler static olarak tanımlanmalıdırlar

Static olarak tanımlanan veri elemanının sadece bir
kopyası vardır ve kendi sınıfının tüm nesneleri o
değişkeni paylaşır.

Statik üyeler nesne tanımlanmadan önce bellekte
yaratılırlar.
STATİK VERİ ELEMANLARI - 2
t2 nesnesi
#include <iostream>
using namespace std;
deger1
class Test {
int deger1;
static int deger2;
t1 nesnesi
};
int main(){
int Test::deger2 = 7;
Test t1;
Test t2, t3;
return 0;
}
deger2
deger1
t3 nesnesi
deger1
STATİK VERİ ELEMANLARI - 3

Bir
bankadaki
tasarruf
hesapları
TasarrufHesabi sınıfı tanımlayalım.
için
bir

Sınıfa ait her nesne belirli bir müşterinin hesap ve
isim bilgisini taşıyacak olsun.

Ayrıca, sınıf her gün kazanılan faizleri hesaba
ekleyecek bir eleman fonksiyona sahip olsun.
STATİK VERİ ELEMANLARI - 4

Böyle bir sınıf için günlük faiz oranını nasıl gösterebiliriz?

Sürekli değiştiği için sabit değil, değişken olmalıdır.

Sınıfın sıradan bir eleman sahası olması halinde, her nesne
kendi kopyasına sahip olacaktır; bu da yalnızca bellek
israfına yol açmakla kalmayacak aynı zamanda faiz oranı
değiştiğinde her nesneyi güncellemeyi gerektirecektir.

Faiz oranını global bir değişken yapmak isteyebilirsiniz; fakat
bu her fonksiyonun değişkeninizin değerini değiştirebileceği
anlamına gelecektir.

İhtiyacımız olan belirli bir sınıf için global bir değişkendir.
STATİK VERİ ELEMANLARI - 5

C++, belirli bir sınıf için global değişken tanımlama imkanını statik
eleman sahalar aracılığıyla sağlar. Sıradan bir eleman saha gibi
işleme sokulmasına rağmen, bildirimi static yapılmış eleman
sahaların, sınıfa ait kaç nesne yaratılırsa yaratılsın, tek bir kopyası
oluşturulur.
class TasarrufHesabi
{ public:
TasarrufHesabi();
void faizEkle()
{ toplam += faizOrani * toplam; }
//...
private:
char isim[30];
float toplam;
static float faizOrani;
//...
};
STATİK VERİ ELEMANLARI - 6

Statik bir eleman sahayı, bildirimini public yapmak
içerisinde erişilebilir kılabilirsiniz:
suretiyle, program
// Eğer faizOrani public ise
void main()
{ TasarrufHesabi hesap1;
hesap1.faizOrani = 0.0005; }

Yukarıdaki programda kullanılan sözdizim meşru fakat aldatıcıdır; çünkü, bütün
TasarrufHesabi nesnelerinin faiz oranının değiştiriliyor olmasına rağmen,
yalnızca hesap1 nesnesinin faiz oranının değiştirildiği izlenimini doğurmaktadır.
Daha iyi bir gösterim yolu aşağıdaki olacaktır:
TasarrufHesabi::faizOrani = 0.0005;

Bu sözdizim değişimin bütün sınıfa uygulanacağı gerçeğini yansıtmaktadır ve
hiçbir TasarrufHesabi nesnesi yaratılmamış olsa bile kullanılabilir.
STATİK VERİ ELEMANLARI - 7

Statik bir eleman sahanın ilk değerini sınıfının yapıcı fonksiyonu
içinden alması mümkün değildir; çünkü ilk değer bir kez atanır
fakat yapıcı fonksiyon birçok kez çağrılabilir.

Statik bir eleman saha, tıpkı global bir değişken gibi, dosya
kapsam alanında ilk değerini almalıdır. Erişim belirteci statik
eleman sahalar için ilk değer alma esnasında etkin değildir;
dolayısıyla private eleman sahalar ilk değerlerini public
eleman sahalar gibi alırlar:
float TasarrufHesabi::faizOrani = 0.0005;
TasarrufHesabi::TasarrufHesabi()
{ //... }
//...
STATİK ELEMAN FONKSİYONLAR - 1

Eğer yalnızca statik eleman sahalara erişim yapan bir fonksiyonunuz
varsa, fonksiyonun bildirimini static yapabilirsiniz:
class TasarrufHesabi
{ public:
TasarrufHesabi();
void faizEkle()
{ toplam += faizOrani * toplam; }
static void faizBelirle( float yeniDeger )
{ faizOrani = yeniDeger; }
//...
private:
char isim[30];
float toplam;
static float faizOrani;
//...
};
STATİK ELEMAN FONKSİYONLAR - 2

Statik eleman fonksiyonlar statik eleman sahalara erişim
için kullanılan sözdizim ile çağrılabilirler:
// Statik eleman fonksiyon çağrımı
void main()
{
TasarrufHesabi hesap1;
hesap1.faizBelirle(0.0005);
TasarrufHesabi::faizBelirle(0.0005);
}

Statik eleman fonksiyonlar belirli bir nesne üzerinde işlem
yapmadıkları için this işaretçisine sahip değillerdir.
STATİK ELEMANLAR


Statik eleman sahalar bütün nesnelerin ihtiyacı olan ortak
kaynakları yönetmek ya da nesnelere ilişkin durum bilgisi
tutmak için kullanılabilir.
Örneğin, herhangi bir anda bir sınıfın kaç nesnesinin bulunduğu
bilgisini statik bir eleman saha üzerinde tutabiliriz:
class Ucak
{
public:
Ucak() { sayac++; }
static int miktarSoyle() { return sayac; }
~Ucak(){ sayac--; }
private:
static int sayac;
};
int Ucak::sayac = 0;
ARKADAŞ SINIFLAR - 1

Bazen iki yada daha fazla sınıfın o kadar işbirliği içerisinde çalışması gerekir ki
birbirlerinin erişim fonksiyonlarını kullanmak yeterince verimli olmayabilir ve
dolayısıyla birbirlerinin özel korumalı (private) eleman sahalarına doğrudan
erişmeleri gerekebilir. Bu tür bir imkan friend anahtar sözcüğünü kullanarak
elde edilir:
class Sinif1
{
friend class Sinif2; // Arkadaş sınıf
private:
int cokGizli;
};
class Sinif2
{
public:
void degistir(Sinif1 nesne1)
};
void Sinif2::degistir(Sinif1 nesne1)
{
nesne1.cokGizli++;
}
ARKADAŞ SINIFLAR - 2
#include <iostream>
using namespace std;
class beta;
class alpha
{
private:
int data1;
public:
alpha() : data1(99) { } //constructor
friend class beta; //beta is a friend class
};
ARKADAŞ SINIFLAR - 3
// all member functions can access private
// alpha data
class beta
{
public: //access private alpha data
void func1(alpha a)
{ cout << “\ndata1=” << a.data1; }
void func2(alpha a)
{ cout << “\ndata1=” << a.data1; }
};
ARKADAŞ SINIFLAR - 4
int main()
{
alpha a;
beta b;
b.func1(a);
b.func2(a);
cout << endl;
return 0;
}
ARKADAŞ SINIFLAR - 5

friend bildirimi public ve private anahtar sözcüklerinden
etkilenmez; sınıf bildiriminin herhangi bir yerinde bulunabilir.

Tanımlanan sınıf kendi özel korumalı eleman sahalarına
erişebilecek arkadaş sınıflar belirleyebilir; fakat, kendisini bir
başka sınıfın arkadaşı ilan edemez.

friend arkadaş sözcüğü tek yönlü erişim imkanı sağlar.
ARKADAŞ SINIFLAR - 6

Arkadaş mekanizmasını kullanmanız halinde yalıtılmış
sınıflarla değil, bir arada düşünülmesi gereken iki yada daha
fazla sınıfla uğraşıyorsunuz demektir. Bu nedenle, bu
mekanizma elden geldiğince seyrek kullanılmalıdır.
ARKADAŞ FONKSİYONLAR - 1

Bütün bir sınıfın yerine, tek bir fonksiyon da arkadaş
(friend) olarak bildirilebilir.
class myclass {
int a, b;
public:
friend int sum(myclass x); // Arkadaş fonksiyon
}

Arkadaş fonksiyonları sınıfa ait değildir, ama bu
fonksiyonların o sınıfa ait private elemanlara erişim
hakkı vardır.
ARKADAŞ FONKSİYONLAR - 1

Örnek1 (Osborne 1998):
#include <iostream>
using namespace std;
class myclass {
int a, b;
public:
friend int sum(myclass x); // Arkadaş fonksiyon
void set_ab(int i, int j);
};
void myclass::set_ab(int i, int j)
{ a = i;
b = j;
}
ARKADAŞ FONKSİYONLAR - 2
// sum() is not a member function of any class.
int sum(myclass x)
{
/* Because sum() is a friend of myclass, it can directly access
a and b. */
return x.a + x.b;
}
int main()
{
myclass n;
n.set_ab(3, 4);
cout << sum(n);
return 0;
}
ARKADAŞ FONKSİYONLAR - 3

Örnek2 (Osborne 1998):
#include <iostream>
using namespace std;
const int IDLE = 0;
const int INUSE = 1;
class C2; // forward declaration
class C1 {
int status; // IDLE if off, INUSE if on screen
// ...
public:
void set_status(int state);
friend int idle(C1 a, C2 b);
};
ARKADAŞ FONKSİYONLAR - 4
class C2 {
int status; // IDLE if off, INUSE if on screen
// ...
public:
void set_status(int state);
friend int idle(C1 a, C2 b);
};
void C1::set_status(int state)
{ status = state; }
void C2::set_status(int state)
{ status = state; }
ARKADAŞ FONKSİYONLAR - 5
int idle(C1 a, C2 b)
{ if(a.status || b.status) return 0;
else return 1; }
int main()
{ C1 x;
C2 y;
x.set_status(IDLE);
y.set_status(IDLE);
if(idle(x, y)) cout << "Screen can be used.\n";
else cout << "In use.\n";
x.set_status(INUSE);
if(idle(x, y)) cout << "Screen can be used.\n";
else cout << "In use.\n";
return 0; }
NESNE DİZİLERİ - 1

Herhangi bir veri tipinde dizi tanımlar gibi nesne dizileri
tanımlayabilirsiniz:
Tarih dogumGunleri[10];

Bir nesne dizisi bildirimi yaptığınızda, yapıcı fonksiyon
dizideki her eleman için çağrılır.

Eğer ilk değer belirtmeden dizilerinizin bildirimini yapmak
istiyorsanız varsayılan yapıcı fonksiyona sahip olmanız
gerekir. Yukarıdaki örnekte, dizinin her elemanı Ocak 1, 1
değerini ilk değer olarak alır.
NESNE DİZİLERİ - 2

Ayrıca, argüman alan yapıcı fonksiyonunuzu açıkça
çağırarak da dizideki her elemana ilk değer atayabilirsiniz.
Eğer dizinin bütünü için yeterince ilk değer belirtmezseniz,
geriye kalan elemanlar için varsayılan yapıcı fonksiyon
çağrılır:
Tarih dogumGunleri[10] = {
Tarih(2,10,1950),
Tarih(9,16,1960),
Tarih(7,31,1953),
Tarih(1,3,1970),
Tarih(12,2,1963)
};
NESNE DİZİLERİ - 3

Eğer sınıf tek bir argüman alan yapıcı fonksiyona sahipse,
ilk değer olarak yalnızca bu argümanı belirtebilirisiniz.

Ayrıca, farklı ilk değer atama tarzlarını karışık da
kullanabilirsiniz:
String mesaj[10]={“Mesajin ilk satiri\n”,
“ikinci satiri\n”,
String(“ucuncu satiri\n”),
String(‘-’,25),
String()};
Kaynaklar

Prof. Dr. Yılmaz Kılıçaslan, Nesneye Yönelik Programlama dersi sunumları

C++ Temel Öğrenim Kılavuzu, Herbert Schildt, Alfa Basım Yayın Dağıtım,
2010.
Download