Bu yazıda nesne yönelimli programlama konusuna devam edeceğiz. Yazı içerisinde kapsülleme, this, sanal metotlar, çok biçimlilik, Metot Ezme, soyutlama, nesne yönelimli programlamada const yapısı, statik olarak tanımlama, nesne yönelimli programlamada arkadaşlık yapısı konularından bahsedeceğim. Ayrıca konunun anlatımını kuvvetlendirmek için pek çok kod örneğine de yazı içerisinde yer verilmiştir.
Kapsülleme
Kapsülleme herhangi bir işlemi gerçekleştirirken arada bir aracı kullanma durumu olarak özetlenebilir. Kapsülleme erişimimizin sınırlanmış olduğu (private) bir takım özelliklere, aracı fonksiyon ve benzeri yollar kullanarak erişmemizi sağlayabilir. Oluşturulan aracı fonksiyonları bir takım kontrol işlemlerini gerçekleştirecek şekilde tasarlayarak, programın daha kontrollü bir şekilde çalışmasını sağlayabilirsiniz.
Aşağıda örnek bir kapsülleme işlemi gerçekleştirilmiştir. Bu örnekte kapsülleme işlemi sayesinde kullanıcının erişiminin sınırlanmış olduğu uzunluk özelliğine erişmesi sağlanmıştır. uzunluk özelliğini kullanabilmek için isim_doldur metodu aracı olarak kullanılmıştır.
#include<iostream>
class isim {
private:
float uzunluk;
public:
void isim_doldur(float leng) {
if (leng < 8 && leng > 34){
std::cout << "isim uzunluğu uygun değil.";
}
else {
uzunluk = leng;
}
}
};
void main() {
isim isim1;
isim1.isim_doldur(10);
}
This
This işaretçi gibi kullanılır, ilgili nesnenin bellek adresini temsil eder. Temel amacı ilgili nesnenin stack’den (yığından) alınan bellek adresini kod hafızasında temsil etmektir. Bu sayede her nesne için kod hafızasında yeni metot oluşturulmaz. Kod hafızasında metodu sadece bir kere oluşturarak her nesne için kullanılabiliriz. Bu durum hafıza kullanımını iyileştirir.
Aşağıda this kullanımına bir örnek verilmiştir. Aşağıdaki metot içerisinde this ifadesi, içinde bulunduğu ilgili nesnenin bellek adresini temsil etmektedir. Metot için girdi olarak bir this parametresi tanımlayamazsınız, çünkü varsayılan olarak sistem kendisi zaten tanımlamaktadır.
#include<iostream>
class alet {
public:
std::string isim;
std::string renk;
float uzunluk;
void doldur(std::string isim, std::string renk, float uzunluk ) {
this->uzunluk = uzunluk;
this->isim = isim;
this->renk = renk;
}
};
void main() {
alet alet1;
alet1.doldur( "ax", "kırmzı", 10.5);
}
Sanal Metotlar
Sanal metotların amaçlarından biri, bir tablo oluşturmak ve türetilmiş sınıfların içindeki sanal metotla aynı isime sahip olan metotlarının bellek adreslerini, bu tabloya yazmaktır. Sanal metot oluşturulduktan sonra, ilgili metodun çağrılması halinde, hangi sınıfa ait olan metodun çalıştırılacağı tablo aracılığı ile bulunur. Sanal metotlar oluşturarak, türetilmiş sınıfların ortak özelliklerini hatasız bir şekilde çağıracak fonksiyonlar da oluşturabilirsiniz.
Aşağıdaki örnek de alet sınıfının içinde virtual void kır() {…} ifadesi vasıtası ile kır sanal metodu tanımlanmıştır. virtual void kır() = 0; şeklinde bir ifade ile işlevsiz bir sanal metot da tanımlayabilirdik. Alet sınıfından türetilen balta, kazma ve çekiç sınıflarının içindeki kır metotlarının bellek adresleri, kır sanal metodu sayesinde oluşturulan tabloya yerleştirilmiştir. İlgili tablo ve yukarı dönüştürme yönteminden faydalanılarak, sanal kır metodu üzerinden, türetilmiş sınıflar içerisindeki tüm kır metotlarına erişilebilmiştir. Ayrıca kırar isminde bir fonksiyon oluşturularak, kır metodunun çağrılma işlemi otomatikleştirilmiştir.
#include<iostream>
class alet {
public:
float uzunluk;
int kuvvet;
std::string isim;
std::string renk;
virtual void kır() {
std::cout << "kırılamaz.";
}
virtual void tamir() {
std::cout << "tamir edilemez.";
}
};
class balta : public alet {
public:
float kesicilik;
void kır() {
std::cout << "kesiciligi daha yuksek bir kirma eylemi basladi.";
}
};
class kazma : public alet {
public:
float kırıcılık;
void kır() {
std::cout << "kaya vb. maddelere karsi daha kuvvetli bir kirma eylemi basladi.";
}
};
class çekiç : public alet {
public:
float tamir_kuvveti;
void kır() {
std::cout << "kirma eylemi basladi.";
}
void tamir() {
std::cout << "tamir ediliyor...";
}
};
void kırar( alet* p) {
p->kır();
}
void main() {
balta B18;
kazma K1;
çekiç C10;
alet* ptrAb = &B18;
alet* ptrAk = &K1;
alet* ptrAc = &C10;
ptrAb->kır();
std::cout << std::endl;
ptrAk->kır();
std::cout << std::endl;
ptrAc->kır();
std::cout << std::endl;
ptrAc->tamir();
std::cout << std::endl;
kırar(ptrAb);
std::cout << std::endl;
kırar(ptrAk);
std::cout << std::endl;
kırar(ptrAc);
std::cout << std::endl;
}
Yukarıdaki programın ekran çıktısı Aşağıdaki gibidir: kesiciligi daha yuksek bir kirma eylemi basladi.
kaya vb. maddelere karsi daha kuvvetli bir kirma eylemi basladi.
kirma eylemi basladi.
tamir ediliyor… kesiciligi daha yuksek bir kirma eylemi basladi.
kaya vb. maddelere karsi daha kuvvetli bir kirma eylemi basladi. kirma eylemi basladi.
Çok Biçimlilik
Bir yapının başka yerlerde başka şekillerde davranması, yani başka özellikler göstermesi olarak düşünülebilir. Yukarıdaki örnek de olduğu gibi araç sınıfından türetilmiş farklı sınıfların aynı metodu farklı şekillerde ele alması çalışma zamanında çok biçimliliğe örnek olarak verilebilir. Daha önce fonksiyonlar konuşu içerisinde anlatmış olduğum, aynı işimler ile farklı fonksiyonlar tanımlama konusu ise, derleme zamanında çok biçimliliğe örnek olarak verilebilir.
Metot Ezme
Aralarında miras alam ilişkisi bulunan sınıfalar, bir üst sınıfın metodunu geçersiz kılabilir. Bu işlem temel sınıftaki metot ile türetilmiş sınıftaki metot isimlerinin aynı olacak şekilde tanımlanması ile gerçekleştirilir. Eğer bu şekilde oluşturulmuş bir metoda sahipseniz ve aynı zamanda temel sınıftaki metodun çalışmasını da istiyorsanız, metot içerisinde temel_sınıf_ismi :: metod_ismi (); şeklinde bir ifade kullanabilirsiniz.
Aşağıdaki örnekle ekrana ” okyanus nerede? burada.” yazdırılacaktır. Eğer bul::ara(); ifadesi kullanılmasaydı ekrana sadece “burada.” yazdırılırdı. Bu şekildeki ifadelerin ne işe yaradığını ileride namespace konusunda daha ayrıntılı ele alacağız.
#include<iostream>
class bul {
public:
void ara() {
std::cout << "okyanus nerede?" << std::endl;
}
};
class bulundu : public bul {
public:
void ara() {
bul::ara();
std::cout << "burada.";
}
};
void main() {
bulundu B1;
B1.ara();
}
Soyutlama
Soyut sınıflar kendinden miras alma yoluyla türetilen sınıfların içeresine, belli metotların yazılmasını zorunlu hale getirir. Soyut sınıflar içerisinde virtual void metot_ismi() = 0; ifadesi vasıtası ile tanımlanmış gövdesiz metotlar barındırır. Ayrıca soyut sınıfların içerisinde değişkenlerde bulunabilir. Eğer bu şekildeki bir sınıfın içerisinde hiçbir değişken yoksa bu sınıf arayüz (interface) olarak adlandırılır. Bu şekilde tanımlanmış gövdesiz metotlar, ilgili metodun bulunduğu sınıftan türetilmiş sınıflar içerisinde bulunmasını zorunlu hale gelir. Soyut sınıflardan nesne üretilemez, soyut sınıfların amacı başka sınıfları yönetmektir.
Aşağıdaki örnekte eğer hesap sınıfından türetilen sınıfların birinde bile maliyet metodu bulunmasaydı, aşağıdaki program hata verirdi.
#include<iostream>
class hesap {
public:
float gelir;
float gider;
virtual void maliyet() = 0;
};
class yol : public hesap {
public:
void maliyet() {}
};
class imalathane : public hesap {
public:
void maliyet() {}
};
class havalanı : public hesap {
public:
void maliyet() {}
};
void main() {
yol y;
imalathane i;
havalanı h;
}
Nesne Yönelimli Programlamada Const Yapısı
Const yapısını hatırlamak gerekirse, const ifadesi ile beraber tanımlanan değişkenlerin değerleri değiştirilemez. Aynı şekilde const ifadesini sınıf içerisindeki değişkenlerin basında kullanarak ilgili değişkenlerin değerlerinin değiştirilmesini engelleyebilirsiniz. Ayrıca herhangi bir nesne oluştururken, nesne oluşturma ifadesinin başına const yazarak, ilgili nesnenin içerisinde bulunan değişkenlerin değerlerinin, değiştirilemez hale gelmesini sağlayabilirsiniz.
Sınıf içerisinde int sınıf_ismi () const { … } ve benzeri bir metot tanımlarsak const ifadesinden sonra gelen parantezler içerisinde sınıf içerisindeki herhangi bir değişkenin değerini değiştirmeyeceğimizi bildirmiş oluruz. Eğer ilgili parantezler içerisinde sınıfın herhangi bir değişkeninin değerini değiştirirsek program hata verecektir.
Eğer int sınıf_ismi ( const sınıf_adı* temsili_isim ) { … } vb. şekilde bir kullanım gerçekleştirilirse, metot içerisinde ilgili nesneye ait herhangi bir değişkenin değerini değiştiremezsiniz. Eğer int sınıf_ismi ( sınıf_adı* const temsili_isim ) { … } vb. şekilde bir kullanım gerçekleştirilirse, metot içerisinde ilgili değişken içerisindeki işaretçi adresini değiştiremezsin ama nesne içerisindeki değişken değerlerine müdahale edebilirsin. Mesela sınıf içerisindeki bir metot const float* ve benzeri bir değer döndürüyor ve bizde döndürülen bu değeri bir değişkene eşitliyorsak, bu değişkeninden uygun bir const değişken olması gerekir.
Statik Olarak Tanımlama
Static olarak tanımlanan bir değişken program main fonksiyonundan çıkana kadar bellekte tutulmaya devam edecektir. Program statik olarak tanımlama gerçekleştirilen ifadeyi tekrar okursa eğer, bu durumu es geçerek hataya sebebiyet vermeyecektir. Mesela static olarak tanımlanan aynı değişkenle iki işlem gerçekleştirirseniz, ikinci işlem birinci işlemin sonucunda elde edilen değer kullanılarak gerçekleştirilecektir.
Nense yönelimli programlamada işe sınıf içerisinde statik bir değişken oluşturursanız, normalde her nesne için her değişken ayrı ayrı oluşturuluyorken, statik olarak tanımlanan değişken, o sınıftan oluşturulan tüm nesneler için aynı olur, yani o sınıftan üretilen tüm nesneler için ilgili değişken ortak olur ve bir nesne ile statik değişken üzerinde yapılan işlem aynı sınıftan üretilen diğer tüm nesneleri etkiler. Sınıf içerisinde statik olarak tanımlanan değişkenlere sınıf içerisinde değer atayamazsınız. Mesela sınıf içerisinde static int zaman; şeklinde bir değişken oluşturduysanız ilgili değişkene sınıf içerisinde değer atayamazsınız. İlgili sınıfın dışında oluşturacağınız, int sınıf_ismi : : zaman = değer; vb. bir ifadeyle ilgili değişkene değer ataması gerçekleştirebilirsiniz.
Statik bir metot statik değişkende de olduğu gibi ortak bir yapı olarak düşünülebilir. Statik bir metot ilgili metodun başına static yazılması yardımıyla oluşturulur. İlgili metot statik olduğu için sınıf_ismi : : statik_metot_ismi; vb. bir ifade yardımıyla, statik değişkenlerde de olduğu gibi, sınıf ismi ile erişilebilirdir.
Ayrıca herhangi bir nesneyi oluştururken, statik olacak şekilde de oluşturulabilirsiniz. Bu sayede ilgili nesne main program sonlanana kadar hafızda kalacaktır. static sınıf_ismi nesne_ismi; vb. bir ifade vasıtası ile statik bir nesne tanımlayabiliriz.
Nesne Yönelimli Programlamada Arkadaşlık Yapısı
Arkadaşlık yapısı yardımı ile ilgili sınıfın gizli (private) olarak tanılanan özelliklerine, çeşitli fonksiyon veya sınıf yapıları için erişim izni vermiş oluruz. Bu izin verme işlemini, ilgili sınıf içerisinde, izin verilecek fonksiyonun veya sınıfın tanım kısmını, basına friend yerleştirerek yazmak vasıtası ile oluşturulabiliriz.
Aşağıdaki programda genel sınıfının gizli (private) olarak tanılanan özelliklerine, özel sınıfı ve ben fonksiyonu aracılığı ile erişilmiştir.
#include<iostream>
class genel {
private:
std::string isim;
public:
friend class özel;
friend std::string ben( genel* g, std::string ad );
};
class özel {
public:
void isimlendir (genel* g, std::string ad ) {
g->isim = ad;
std::cout << g->isim;
}
};
std::string ben( genel* g, std::string ad ) {
g->isim = ad;
return g->isim;
}
void main() {
genel p;
genel e;
özel ö;
std::cout << ben(&p,"Ahmet Volkan");
ö.isimlendir( &e, "Yazıcıoglu");
}