Selamlar herkese.
Hatırlayacağınız üzere, bir önceki nesne yönelimli programlama yazımda sizlerle birlikte nesne yönelimli programlamaya giriş yapmıştık ve çok şey öğrenmiştik. (Okumadıysanız buradan erişebilirsiniz.)
Bu yazıda sizlere daha ileri seviye nesne yönelimli programlama anlatacağım. Hazırsanız başlayalım.
Araba beyni örneğimiz üzerinden devam edeceğiz. Hatırlarsanız "genel amaçlı" bir araba beyni yazmıştık. İçerisinde motorun ömrü ile alakalı bilgileri, benzin bilgilerini barındırıyordu. Kod gelsin:
[CODE lang="cpp" title="Önceki yazımdaki kod"]#include <iostream>
#define DEPOYU_FULLE_USTA 45 //Arabaların yakıt tankı genelde 45 litre yakıt alır.
class araba{
private:
float benzin_degeri = 3.4f; //3.4 litre benzinimiz var diyelim.
protected:
int arabanin_motor_omru = 20; //Bu arabamızın 20 yıllık bir motor ömrü olsun.
public:
araba(float ilk_yakit_miktari){ //Arabamızı ilk aldığımızda benzin olmalı, değil mi? Buna "constructor" denir. Arabamızı aldığımız zaman olacak olan özelliklerin ne olduğunu taşır diyebilirim.
benzin_degeri = ilk_yakit_miktari;
}
inline float ne_kadar_benzin_kaldi(){ //inline: derleyiciye bu fonksiyonun direkt olarak tanıtıldığını söyler. Gerekli değildir çoğu durumda fakat yazmaya alışmak kesinlikle iyi olacaktır. Daha sonraki yazılarımda üzerinde durabileceğim bir konu.
return benzin_degeri; //Evet, private bir değişkeni, public bir fonksiyon aracılığı ile döndürebiliriz. Bu tür fonksiyonlara "getter" denmektedir.
}
inline void benzin_doldur(float yeni_deger){ //void: Hiç bir değer döndürmeyen fonksiyonlara koyulur. Sadece işi yapar ve çekilir. O kadar. Bir nevi "tetikçidir."
benzin_degeri += yeni_deger; //Sonuçta benzini boşaltıp, tekrar benzin koymadık. Üzerine ekledik.
std::cout<<yeni_deger<<" litre kadar benzin koydum. "<<(yeni_deger == 45?"Depoyu fulledim.\n":"\n");
}
inline void benzin_tankini_bosalt(){
benzin_degeri = 0.0f;
std::cout<<"Benzin tanki artik bos. En yakin benzincide arabaya benzin koyun.\n";
}
};
int main(){
araba gicir_gicir_bir_araba(DEPOYU_FULLE_USTA); //Full depo benzinle gelsin arabamız.
gicir_gicir_bir_araba.benzin_tankini_bosalt(); //Benzinimiz bitti diyelim.
gicir_gicir_bir_araba.benzin_doldur(DEPOYU_FULLE_USTA);
std::cout<<gicir_gicir_bir_araba.ne_kadar_benzin_kaldi()<<" litre kadar benzinimiz kaldi.\n";
return 0;
}[/CODE]
Şimdi bu kodumuzu, araba firmalarına göre değişebilecek hale getireceğiz. Farzedelim ki siz gerçekten de araba beyni yazan ve bunları araba firmalarına satan bir şirketsiniz. Her araç modeli için baştan bir beyin prototipi yapıp, onun üzerinde mi ilerlersiniz? Yoksa her araba için ortak kod olacak şeyleri bir yerde barındırıp, o kodun üzerinden mi devam edersiniz?
Tabii ki ikinci seçenek üzerinden gitmek sizin firmanıza hem zaman, hem de para tasarrufu sağlayacaktır. Ayrıca bu prototip çok modüler olacağı için firmanızı yıllarca idare edecektir.
Önceki yazımda ve yukarıda bulunan kodu bir prototip olarak kullanabiliriz, çünkü her araba için ortak şeyleri barındırmaktadır. Örneğin, her arabanın beyninde benzin hakkında bilgiler vs. olmalıdır. Kollarımızı sıvayıp kod yazalım!
Kod bölümü
Önceki yazıda bahsetmediğim bir kısım ise, sınıfların ayrı dosyalarda prototiplendirilebileceğidir. Bu prototiplendirme ilk başta biraz saçma gibi gözükse de, kod okunaklığını ve kod modülerliğini arttırmak amacıyla tercih edilebilecek bir olaydır. Burada sizlere C++ dilinin "interfaces" özelliğini öğretmek için bu özelliği kullanacağım ve abstract sınıflar ile çalışacağım ilk başta.
Sizlere daha temiz bir kod yazmayı öğretmek adına, sınıf prototiplemesini ve sınıf içerisindeki fonksiyonların tanımını ayrı dosyalarda yapıyorum. Windows, Linux ve macOS sistemlerde farklı yöntemler ile nasıl derleme alacağınızı öğrenmek için aşağıdaki sürprizbozan butonlarını kullanabilirsiniz.
Öncelikle "araba_beyni.h" dosyamıza bakalım:
[CODE lang="cpp" title="araba_beyni.h"]/*
"araba_beyni.h"
Araba beyni hakkındaki tanımları ve araba beyninin sınıf prototipini tutar.
*/
#include <string>
#define DEPOYU_FULLE_USTA 45 //Arabaların yakıt tankı genelde 45 litre yakıt alır.
/*
Temiz kod önerileri: Yukarıdaki gibi sabit tanımlar her zaman .h, .hpp uzantılı header dosyalarında
tutulmalıdır. Kodun temizliğini yükselten önemli bir faktördür.
*/
class araba{
protected: //Bilindiği üzere, kalıtım işlerinde protected yapısı kullanılmalıdır.
float benzin_degeri = 3.4f; //3.4 litre benzinimiz var diyelim.
int arabanin_motor_omru = 20; //Bu arabamızın 20 yıllık bir motor ömrü olsun.
public:
araba(float ilk_yakit_miktari); //Constructor'ımız virtual yapıda olamaz. Fakat herhangi bir tanım yapmadık, bunu araba_beyni.cpp dosyamızda tanımlarız.
/*
"virtual" anahtar sözcüğü, bu tarz "abstract" fonksiyon ve sınıflar oluşturmamız için tasarlanmıştır.
"virtual" anahtar sözcüğü ile tanımlanıp aşağıdaki gibi fonksiyonlara "0 değeri atanmış" ise, o fonksiyon
"ham sanal fonksiyon" ("pure virtual function") olarak geçer.
*/
/*
"Her şey iyi hoş, peki abstract sınıflar nedir?"
Efendim abstract sınıflar, bu tarz "bir kalıp üzerine ilerleyen" sınıfların temelidir. Yani bizim sınıflarımız, bu abstract sınıfı kalıtır ve
içindeki fonksiyon, değişken vs. gibi şeyleri kalıtır. Yazının başlarında da bahsettiğim gibi, farklı üreticilere araba üretme kaynağımız bu sınıf olacaktır.
Aşağıda bir adet fonksiyon "ham sanal fonksiyon" olduğundan dolayı bu sınıf bir "abstract" sınıftır.
*/
float ne_kadar_benzin_kaldi();
void benzin_doldur(float yeni_deger);
void benzin_tankini_bosalt();
virtual std::string bakim_lazim_mi() = 0;
};[/CODE]
Şimdi de, "araba_beyni.h" dosyamızda tanımlamış olduğumuz bu sınıfı, "araba_beyni.cpp" aracılığı ile dolduralım.
[CODE lang="cpp" title="araba_beyni.cpp"]/*Arabaların genel özellikleri...*/
#include "araba_beyni.h"
araba::araba(float ilk_yakit_miktari){ // "::" işaretine "scope resolution" denmektedir. Araba sınıfında bulunan bu fonksiyondan bahsettiğim anlaşılır böylece.
benzin_degeri = ilk_yakit_miktari;
}
float araba::ne_kadar_benzin_kaldi(){
return benzin_degeri;
}
void araba::benzin_doldur(float yeni_deger){
benzin_degeri = yeni_deger;
}
void araba::benzin_tankini_bosalt(){
benzin_degeri = 0.0f;
}
/*Dikkat ederseniz, ham sanal fonksiyon olan bakim_lazim_mi fonksiyonunu doldurmadık. Bunu arabalarımız dolduracaktır.*/
[/CODE]
Şimdi, sizlerle birlikte main.cpp içerisinde bir Toyota araç oluşturalım. Toyota aracımızın genel özellikleri olarak:
[CODE lang="cpp" title="main.cpp"]#include "araba_beyni.h"
#include <iostream>
class Toyota : public araba{ //Toyota, bir araba olduğu için araba sınıfının özelliklerini taşır.
public:
Toyota(float ilk_yakit) : araba(ilk_yakit){ //Araba sınıfının constructor'ını çağırdık.
arabanin_motor_omru = 100; //Bir rivayete göre Toyota'lar 100 yıl yaşar.
}
~Toyota(){ //Destructor. Toyota ile işimiz bitince ne olacağını buraya yazacağız. Örneğin, sınıf içerisinde pointer tanımladıysak o pointer'ı burada silmeliyiz ki uygulamamızda "memory leak" açığı oluşmasın.
//Destructor mantığını daha iyi anlamak için pointer'ları daha iyi anlamanız gerekmektedir.
std::cout<<"Bizi tercih ettiginiz icin tesekkur ederiz.\n";
}
void get_motor_omru(){std::cout<<arabanin_motor_omru<<std::endl;}
std::string bakim_lazim_mi(){
return "Benim yagimi degistirmesen de benim icin sorun degil adamim.";
}
};
int main(){
Toyota corolla(35);
corolla.get_motor_omru();
std::cout<<"Kullanilabilir benzin miktari (litre) : "<<corolla.ne_kadar_benzin_kaldi()<<std::endl
<<"Bakim durumu : "<<corolla.bakim_lazim_mi()<<std::endl;
return 0;
}[/CODE]
Evet, bu yazı bu kadardı. Okuduğunuz için teşekkür ederim. Umarım kalıtım mantığını, C++ dilinde daha temiz kod yazmayı ve destructor'ları anlamışsınızdır; bir dahaki yazımda buluşana dek hoşçakalın!
Hatırlayacağınız üzere, bir önceki nesne yönelimli programlama yazımda sizlerle birlikte nesne yönelimli programlamaya giriş yapmıştık ve çok şey öğrenmiştik. (Okumadıysanız buradan erişebilirsiniz.)
Bu yazıda sizlere daha ileri seviye nesne yönelimli programlama anlatacağım. Hazırsanız başlayalım.
Araba beyni örneğimiz üzerinden devam edeceğiz. Hatırlarsanız "genel amaçlı" bir araba beyni yazmıştık. İçerisinde motorun ömrü ile alakalı bilgileri, benzin bilgilerini barındırıyordu. Kod gelsin:
[CODE lang="cpp" title="Önceki yazımdaki kod"]#include <iostream>
#define DEPOYU_FULLE_USTA 45 //Arabaların yakıt tankı genelde 45 litre yakıt alır.
class araba{
private:
float benzin_degeri = 3.4f; //3.4 litre benzinimiz var diyelim.
protected:
int arabanin_motor_omru = 20; //Bu arabamızın 20 yıllık bir motor ömrü olsun.
public:
araba(float ilk_yakit_miktari){ //Arabamızı ilk aldığımızda benzin olmalı, değil mi?
benzin_degeri = ilk_yakit_miktari;
}
inline float ne_kadar_benzin_kaldi(){ //inline: derleyiciye bu fonksiyonun direkt olarak tanıtıldığını söyler. Gerekli değildir çoğu durumda fakat yazmaya alışmak kesinlikle iyi olacaktır. Daha sonraki yazılarımda üzerinde durabileceğim bir konu.
return benzin_degeri; //Evet, private bir değişkeni, public bir fonksiyon aracılığı ile döndürebiliriz. Bu tür fonksiyonlara "getter" denmektedir.
}
inline void benzin_doldur(float yeni_deger){ //void: Hiç bir değer döndürmeyen fonksiyonlara koyulur. Sadece işi yapar ve çekilir. O kadar. Bir nevi "tetikçidir."
benzin_degeri += yeni_deger; //Sonuçta benzini boşaltıp, tekrar benzin koymadık. Üzerine ekledik.
std::cout<<yeni_deger<<" litre kadar benzin koydum. "<<(yeni_deger == 45?"Depoyu fulledim.\n":"\n");
}
inline void benzin_tankini_bosalt(){
benzin_degeri = 0.0f;
std::cout<<"Benzin tanki artik bos. En yakin benzincide arabaya benzin koyun.\n";
}
};
int main(){
araba gicir_gicir_bir_araba(DEPOYU_FULLE_USTA); //Full depo benzinle gelsin arabamız.
gicir_gicir_bir_araba.benzin_tankini_bosalt(); //Benzinimiz bitti diyelim.
gicir_gicir_bir_araba.benzin_doldur(DEPOYU_FULLE_USTA);
std::cout<<gicir_gicir_bir_araba.ne_kadar_benzin_kaldi()<<" litre kadar benzinimiz kaldi.\n";
return 0;
}[/CODE]
Şimdi bu kodumuzu, araba firmalarına göre değişebilecek hale getireceğiz. Farzedelim ki siz gerçekten de araba beyni yazan ve bunları araba firmalarına satan bir şirketsiniz. Her araç modeli için baştan bir beyin prototipi yapıp, onun üzerinde mi ilerlersiniz? Yoksa her araba için ortak kod olacak şeyleri bir yerde barındırıp, o kodun üzerinden mi devam edersiniz?
Tabii ki ikinci seçenek üzerinden gitmek sizin firmanıza hem zaman, hem de para tasarrufu sağlayacaktır. Ayrıca bu prototip çok modüler olacağı için firmanızı yıllarca idare edecektir.
Önceki yazımda ve yukarıda bulunan kodu bir prototip olarak kullanabiliriz, çünkü her araba için ortak şeyleri barındırmaktadır. Örneğin, her arabanın beyninde benzin hakkında bilgiler vs. olmalıdır. Kollarımızı sıvayıp kod yazalım!
Kod bölümü
Önceki yazıda bahsetmediğim bir kısım ise, sınıfların ayrı dosyalarda prototiplendirilebileceğidir. Bu prototiplendirme ilk başta biraz saçma gibi gözükse de, kod okunaklığını ve kod modülerliğini arttırmak amacıyla tercih edilebilecek bir olaydır. Burada sizlere C++ dilinin "interfaces" özelliğini öğretmek için bu özelliği kullanacağım ve abstract sınıflar ile çalışacağım ilk başta.
Sizlere daha temiz bir kod yazmayı öğretmek adına, sınıf prototiplemesini ve sınıf içerisindeki fonksiyonların tanımını ayrı dosyalarda yapıyorum. Windows, Linux ve macOS sistemlerde farklı yöntemler ile nasıl derleme alacağınızı öğrenmek için aşağıdaki sürprizbozan butonlarını kullanabilirsiniz.
Öncelikle, ben burada Code::Blocks kullanacağım. Dev-C++ kullanıcısı iseniz şiddetle geçmenizi tavsiye ederim.
Code::Blocks'ta boş bir proje açmanızı tavsiye ederim. Gönlünüz kadar temiz bir ekran gelecektir:
Sol üstte gördüğünüz "File" menüsünün altındaki "New" sekmesinde bulunan "Class" seçeneğini seçiyoruz.
Daha sonra karşımıza sınıf oluşturma diyalogu geliyor, benim yaptığım gibi yapabilirsiniz:
Dosyaları oluşturduktan sonra bizim boş projeye ekleyip eklememek istediğimizi soracaktır:
"Yes" tuşuna basarak ilerledikten sonra, projemizin hangi kısımlarına eklemek istediğimizi soruyor; projeyi Debug modunda derlerken mi eklensin, yoksa Release modunda mı? Biz her ikisinde de bu sınıfımızın olmasını istiyoruz ve iki şıkkı da seçiyoruz:
Tebrikler, kod yazmaya hazırsınız!
Code::Blocks'ta boş bir proje açmanızı tavsiye ederim. Gönlünüz kadar temiz bir ekran gelecektir:
Sol üstte gördüğünüz "File" menüsünün altındaki "New" sekmesinde bulunan "Class" seçeneğini seçiyoruz.
Daha sonra karşımıza sınıf oluşturma diyalogu geliyor, benim yaptığım gibi yapabilirsiniz:
Dosyaları oluşturduktan sonra bizim boş projeye ekleyip eklememek istediğimizi soracaktır:
"Yes" tuşuna basarak ilerledikten sonra, projemizin hangi kısımlarına eklemek istediğimizi soruyor; projeyi Debug modunda derlerken mi eklensin, yoksa Release modunda mı? Biz her ikisinde de bu sınıfımızın olmasını istiyoruz ve iki şıkkı da seçiyoruz:
Tebrikler, kod yazmaya hazırsınız!
Aslında bu yöntem Windows'ta da biraz değiştirilerek kullanılabilir fakat sizin daha çaylak bir programcı olduğunuzu düşünerek Windows safhasını es geçiyorum.
Makefile kullanarak bu kodları derlemek çok daha kolay; sadece "Makefile" isminde bir dosya oluşturun ve içine şunları yazın:
[CODE lang="makefile" title="Makefile'ımız, bunu kod dosyalarıyla aynı klasöre koyun."]DERLEYICI = c++ #UNIX, Linux, macOS gibi sistemlerde GCC C++ derleyicisinin ortak adıdır.
CPP_SURUMU = c++11
ARABA_BEYNI_DOSYA_ADI = araba_beyni.cpp
ANA_DOSYA_DOSYA_ADI = main.cpp #Sınıf tanımları dosyamız olan araba_beyni.cpp maalesef int main() içermediğinden çalışmaz...
CIKTI_DOSYA_ADI = araba_beyni_derlenmis
all:
$(DERLEYICI) $(ARABA_BEYNI_DOSYA_ADI) $(ANA_DOSYA_DOSYA_ADI) -o $(CIKTI_DOSYA_ADI) -std=$(CPP_SURUMU)[/CODE]
Bu Makefile'ımızı, bizim dosyalarımızın yanına kaydetmeliyiz. Durum şöyle olmalı:
Daha sonra bir uçbirim (terminal) açıp,
Uyarı: Makefile hakkında bir bilginiz yoksa lütfen Makefile dosyası ile oynamayın.
Makefile kullanarak bu kodları derlemek çok daha kolay; sadece "Makefile" isminde bir dosya oluşturun ve içine şunları yazın:
[CODE lang="makefile" title="Makefile'ımız, bunu kod dosyalarıyla aynı klasöre koyun."]DERLEYICI = c++ #UNIX, Linux, macOS gibi sistemlerde GCC C++ derleyicisinin ortak adıdır.
CPP_SURUMU = c++11
ARABA_BEYNI_DOSYA_ADI = araba_beyni.cpp
ANA_DOSYA_DOSYA_ADI = main.cpp #Sınıf tanımları dosyamız olan araba_beyni.cpp maalesef int main() içermediğinden çalışmaz...
CIKTI_DOSYA_ADI = araba_beyni_derlenmis
all:
$(DERLEYICI) $(ARABA_BEYNI_DOSYA_ADI) $(ANA_DOSYA_DOSYA_ADI) -o $(CIKTI_DOSYA_ADI) -std=$(CPP_SURUMU)[/CODE]
Bu Makefile'ımızı, bizim dosyalarımızın yanına kaydetmeliyiz. Durum şöyle olmalı:
Daha sonra bir uçbirim (terminal) açıp,
make
yazın, ve kodumuz derlensin!Uyarı: Makefile hakkında bir bilginiz yoksa lütfen Makefile dosyası ile oynamayın.
Öncelikle "araba_beyni.h" dosyamıza bakalım:
[CODE lang="cpp" title="araba_beyni.h"]/*
"araba_beyni.h"
Araba beyni hakkındaki tanımları ve araba beyninin sınıf prototipini tutar.
*/
#include <string>
#define DEPOYU_FULLE_USTA 45 //Arabaların yakıt tankı genelde 45 litre yakıt alır.
/*
Temiz kod önerileri: Yukarıdaki gibi sabit tanımlar her zaman .h, .hpp uzantılı header dosyalarında
tutulmalıdır. Kodun temizliğini yükselten önemli bir faktördür.
*/
class araba{
protected: //Bilindiği üzere, kalıtım işlerinde protected yapısı kullanılmalıdır.
float benzin_degeri = 3.4f; //3.4 litre benzinimiz var diyelim.
int arabanin_motor_omru = 20; //Bu arabamızın 20 yıllık bir motor ömrü olsun.
public:
araba(float ilk_yakit_miktari); //Constructor'ımız virtual yapıda olamaz. Fakat herhangi bir tanım yapmadık, bunu araba_beyni.cpp dosyamızda tanımlarız.
/*
"virtual" anahtar sözcüğü, bu tarz "abstract" fonksiyon ve sınıflar oluşturmamız için tasarlanmıştır.
"virtual" anahtar sözcüğü ile tanımlanıp aşağıdaki gibi fonksiyonlara "0 değeri atanmış" ise, o fonksiyon
"ham sanal fonksiyon" ("pure virtual function") olarak geçer.
*/
/*
"Her şey iyi hoş, peki abstract sınıflar nedir?"
Efendim abstract sınıflar, bu tarz "bir kalıp üzerine ilerleyen" sınıfların temelidir. Yani bizim sınıflarımız, bu abstract sınıfı kalıtır ve
içindeki fonksiyon, değişken vs. gibi şeyleri kalıtır. Yazının başlarında da bahsettiğim gibi, farklı üreticilere araba üretme kaynağımız bu sınıf olacaktır.
Aşağıda bir adet fonksiyon "ham sanal fonksiyon" olduğundan dolayı bu sınıf bir "abstract" sınıftır.
*/
float ne_kadar_benzin_kaldi();
void benzin_doldur(float yeni_deger);
void benzin_tankini_bosalt();
virtual std::string bakim_lazim_mi() = 0;
};[/CODE]
Şimdi de, "araba_beyni.h" dosyamızda tanımlamış olduğumuz bu sınıfı, "araba_beyni.cpp" aracılığı ile dolduralım.
[CODE lang="cpp" title="araba_beyni.cpp"]/*Arabaların genel özellikleri...*/
#include "araba_beyni.h"
araba::araba(float ilk_yakit_miktari){ // "::" işaretine "scope resolution" denmektedir. Araba sınıfında bulunan bu fonksiyondan bahsettiğim anlaşılır böylece.
benzin_degeri = ilk_yakit_miktari;
}
float araba::ne_kadar_benzin_kaldi(){
return benzin_degeri;
}
void araba::benzin_doldur(float yeni_deger){
benzin_degeri = yeni_deger;
}
void araba::benzin_tankini_bosalt(){
benzin_degeri = 0.0f;
}
/*Dikkat ederseniz, ham sanal fonksiyon olan bakim_lazim_mi fonksiyonunu doldurmadık. Bunu arabalarımız dolduracaktır.*/
[/CODE]
Şimdi, sizlerle birlikte main.cpp içerisinde bir Toyota araç oluşturalım. Toyota aracımızın genel özellikleri olarak:
- Motoru çok dayanıklı olacak,
- 35 litre benzin ile gelecek.
[CODE lang="cpp" title="main.cpp"]#include "araba_beyni.h"
#include <iostream>
class Toyota : public araba{ //Toyota, bir araba olduğu için araba sınıfının özelliklerini taşır.
public:
Toyota(float ilk_yakit) : araba(ilk_yakit){ //Araba sınıfının constructor'ını çağırdık.
arabanin_motor_omru = 100; //Bir rivayete göre Toyota'lar 100 yıl yaşar.
}
~Toyota(){ //Destructor. Toyota ile işimiz bitince ne olacağını buraya yazacağız. Örneğin, sınıf içerisinde pointer tanımladıysak o pointer'ı burada silmeliyiz ki uygulamamızda "memory leak" açığı oluşmasın.
//Destructor mantığını daha iyi anlamak için pointer'ları daha iyi anlamanız gerekmektedir.
std::cout<<"Bizi tercih ettiginiz icin tesekkur ederiz.\n";
}
void get_motor_omru(){std::cout<<arabanin_motor_omru<<std::endl;}
std::string bakim_lazim_mi(){
return "Benim yagimi degistirmesen de benim icin sorun degil adamim.";
}
};
int main(){
Toyota corolla(35);
corolla.get_motor_omru();
std::cout<<"Kullanilabilir benzin miktari (litre) : "<<corolla.ne_kadar_benzin_kaldi()<<std::endl
<<"Bakim durumu : "<<corolla.bakim_lazim_mi()<<std::endl;
return 0;
}[/CODE]
Evet, bu yazı bu kadardı. Okuduğunuz için teşekkür ederim. Umarım kalıtım mantığını, C++ dilinde daha temiz kod yazmayı ve destructor'ları anlamışsınızdır; bir dahaki yazımda buluşana dek hoşçakalın!