Yazılım geliştirme dünyası, her geçen gün daha karmaşık ve büyük projelerle karşı karşıya kalmaktadır. Bu karmaşıklığı yönetmek, kodun yeniden kullanılabilirliğini artırmak ve bakımı kolay sistemler inşa etmek için çeşitli programlama paradigmaları ortaya çıkmıştır. C++, hem düşük seviyeli sistem programcılığına izin veren güçlü bir dil olması hem de yüksek seviyeli, soyutlamalarla dolu uygulamalar geliştirmeye olanak tanımasıyla öne çıkar. C++'ın bu çok yönlülüğünün merkezinde ise Nesne Yönelimli Programlama (OOP) yatmaktadır. OOP, gerçek dünyadaki varlıkları ve onların etkileşimlerini modelleyerek yazılım geliştirmeye bambaşka bir boyut kazandırır.
OOP, bir yazılım sistemini, birbirleriyle etkileşime giren nesneler topluluğu olarak görür. Her nesne, kendi verilerine (niteliklerine) ve bu veriler üzerinde işlem yapan davranışlara (metotlara) sahiptir. Bu yaklaşım, kodun daha modüler, anlaşılır, esnek ve yeniden kullanılabilir olmasını sağlar. Özellikle büyük ölçekli projelerde, OOP'nin sunduğu bu avantajlar, geliştirme sürecini önemli ölçüde hızlandırır ve hataların azalmasına yardımcı olur.
Bu kapsamlı rehberde, C++'da Nesne Yönelimli Programlamanın temel taşlarını ayrıntılı olarak inceleyeceğiz. Kavramsal açıklamaların yanı sıra, pratik kod örnekleri ve sıkça karşılaşılan senaryolar üzerinden konuları pekiştireceğiz. Amacımız, C++ ile sağlam, bakımı kolay ve ölçeklenebilir nesne yönelimli uygulamalar geliştirmek için size sağlam bir temel sunmaktır.
OOP'nin Temel Prensipleri (Dört Temel Taşı):
Nesne Yönelimli Programlamanın gücü, dört temel prensip üzerine kuruludur. Bu prensipler, yazılım tasarımında esneklik, sürdürülebilirlik ve yeniden kullanılabilirlik sağlamak için birlikte çalışır. Bu prensipler şunlardır:
Sınıflar ve Nesneler: OOP'nin Yapı Taşları
OOP'nin temel yapı taşları sınıflar ve nesnelerdir.
* Sınıf (Class): Bir sınıf, belirli bir türdeki nesnelerin şablonu veya blueprint'idir. Nesnelerin sahip olacağı özellikleri (veri üyeleri/attributes) ve yapabileceği eylemleri (metotlar/methods) tanımlar. Sınıflar, kendi başlarına bellek üzerinde yer kaplamazlar; sadece nesne oluşturmak için bir kılavuz sağlarlar. Örneğin, `Araba` bir sınıftır.
* Nesne (Object): Bir sınıfın gerçek bir örneğidir. Bellekte yer kaplar ve sınıfın tanımladığı özelliklere belirli değerler atanmış ve davranışlara sahip somut bir varlıktır. Örneğin, "benim arabam" veya "komşunun arabası" birer `Araba` nesnesidir. Her nesne, sınıfının bir örneğidir ve kendi veri üyelerinin kopyalarına sahiptir.
Kurucular (Constructors) ve Yıkıcılar (Destructors): Nesne Yaşam Döngüsü
Her nesnenin bir yaşam döngüsü vardır: oluşturulur, kullanılır ve yok edilir. C++'da bu döngü kurucular ve yıkıcılar tarafından yönetilir.
* Kurucu (Constructor): Bir sınıfın nesnesi oluşturulduğunda otomatik olarak çağrılan özel bir metottur. Amacı, nesnenin veri üyelerini başlatmak ve nesneyi kullanılabilir bir duruma getirmektir. Kurucunun dönüş tipi olmaz ve sınıf adıyla aynı ada sahiptir. Birden fazla kurucu tanımlanabilir (kurucu aşırı yüklemesi), bu da nesnelerin farklı yollarla başlatılmasına olanak tanır.
* Varsayılan Kurucu (Default Constructor): Parametre almayan kurucudur. Eğer siz bir kurucu tanımlamazsanız, C++ derleyicisi sizin için boş bir varsayılan kurucu oluşturur.
* Parametreli Kurucu (Parameterized Constructor): Parametre alan kurucudur. Nesne oluşturulurken başlangıç değerleri sağlamak için kullanılır.
* Kopyalama Kurucusu (Copy Constructor): Var olan bir nesneden yeni bir nesne oluşturmak için kullanılır.
* Yıkıcı (Destructor): Bir sınıfın nesnesi kapsam dışına çıktığında veya `delete` operatörü ile bellekten silindiğinde otomatik olarak çağrılan özel bir metottur. Amacı, nesnenin kullandığı kaynakları (bellek, dosya tanıtıcıları, ağ bağlantıları vb.) serbest bırakmaktır. Yıkıcının da dönüş tipi olmaz, sınıf adıyla aynı ada sahiptir ancak önüne bir tilde (~) işareti eklenir ve parametre almaz. Bir sınıfta sadece bir yıkıcı olabilir. Kalıtım hiyerarşilerinde sanal yıkıcıların (virtual destructors) kullanılması, türetilmiş sınıf nesnelerinin doğru şekilde yok edilmesi için kritik öneme sahiptir. Aksi takdirde, temel sınıf işaretçisi üzerinden türetilmiş sınıf nesnesi silindiğinde sadece temel sınıfın yıkıcısı çağrılabilir ve bu da bellek sızıntılarına yol açabilir.
Erişim Belirleyiciler (Access Specifiers): Veri Güvenliği
C++'da sınıfların üyelerine (veri ve metotlar) dışarıdan nasıl erişileceğini belirlemek için üç ana erişim belirleyici kullanılır:
* public: Bu erişim belirleyiciye sahip üyeler, sınıf içinden ve dışından, yani herkes tarafından erişilebilir. Bir sınıfın arayüzünü tanımlayan metotlar ve veriler genellikle `public` olarak işaretlenir. Örneğin, bir `Araba` nesnesinin `gazaBas()` metodu `public` olmalıdır, çünkü kullanıcı bu metodu çağırarak arabayı hızlandırmak ister.
* private: Bu erişim belirleyiciye sahip üyeler, sadece sınıfın kendi metotları tarafından erişilebilir. Dışarıdan doğrudan erişilemezler. Kapsülleme prensibini uygulamak için kullanılırlar. Bir sınıfın dahili çalışma detaylarını ve hassas verilerini korumak için idealdir. Örneğin, `Araba` sınıfının `mevcutHiz` değişkeni `private` olmalıdır; dışarıdan direkt değiştirilememeli, sadece `gazaBas()` veya `frenYap()` gibi kontrollü metotlar aracılığıyla güncellenmelidir.
* protected: Bu erişim belirleyiciye sahip üyeler, sınıfın kendi metotları ve o sınıftan türetilmiş sınıfların metotları tarafından erişilebilir. `private` ile `public` arasında bir erişim seviyesi sunar. Özellikle kalıtım kullanılırken, temel sınıfın belirli üyelerinin türetilmiş sınıflar tarafından erişilebilmesini ancak dışarıdan gizli kalmasını sağlamak için kullanışlıdır.
İleri OOP Konuları ve Tasarım Prensipleri
* Saf Sanal Fonksiyonlar ve Soyut Sınıflar: Daha önce soyutlama bölümünde de bahsettiğimiz gibi, C++'da saf sanal fonksiyonlar (`= 0` ile işaretlenir) ve bunları içeren sınıflar (soyut sınıflar), arayüz tanımlamak için kullanılır. Bir sınıfın, alt sınıfları tarafından mutlaka uygulanması gereken bir davranışı olduğunu belirtmenin en güçlü yoludur. Soyut sınıflar, genellikle bir kalıtım hiyerarşisinin başında yer alır ve genel bir konsepti temsil eder. Onlardan türetilen somut sınıflar, bu soyut kavramı kendi özgün şekillerinde gerçekleştirir. Örneğin, `GeometrikSekil` soyut bir sınıf olabilir ve `alanHesapla()` adında saf sanal bir metoda sahip olabilir. `Daire` ve `Kare` gibi somut sınıflar bu metodu kendi şekillerine göre uygulamak zorundadır.
* Sanal Yıkıcılar: Kalıtım ve çok biçimlilikle çalışırken, temel sınıf işaretçisi üzerinden türetilmiş sınıf nesnesini `delete` ettiğinizde, türetilmiş sınıfın yıkıcısının doğru bir şekilde çağrılmasını sağlamak için temel sınıftaki yıkıcının `virtual` olarak tanımlanması zorunludur. Aksi takdirde, sadece temel sınıfın yıkıcısı çağrılır ve bu da türetilmiş sınıfın dinamik olarak ayırdığı kaynakların serbest bırakılmamasına ve bellek sızıntılarına yol açabilir. Bu, C++'da sıkça yapılan bir hatadır ve önüne geçmek için önemli bir kuraldır.
Örnek Uygulama Senaryosu: Hayvan Hiyerarşisi
Çok biçimlilik ve kalıtımı daha iyi anlamak için popüler bir örnek olan hayvan hiyerarşisini inceleyelim.
Bu görsel, bir `Hayvan` temel sınıfından `Kedi` ve `Köpek` gibi türetilmiş sınıfların nasıl oluşturulabileceğini görselleştirebilir. Her hayvanın genel bir `sesCikar()` davranışı vardır, ancak her alt sınıf bu davranışı kendine özgü bir şekilde uygular. Bir hayvan listesini dönerken, her hayvanın kendi sesini çıkarmasını sağlamak için çalışma zamanı çok biçimliliğinden faydalanırız.
Faydaları ve Dezavantajları
Her programlama paradigması gibi, OOP'nin de kendine göre avantajları ve dezavantajları vardır.
Faydaları:
* Kodun Yönetilebilirliği ve Modülerlik: Kod, bağımsız ve ilişkili birimler (nesneler) halinde düzenlenir. Bu, büyük projelerin daha kolay yönetilmesini ve her bir parçanın ayrı ayrı geliştirilip test edilmesini sağlar.
* Yeniden Kullanılabilirlik: Sınıflar bir kez yazıldıktan sonra, farklı projelerde veya aynı projenin farklı yerlerinde tekrar tekrar kullanılabilir. Kalıtım da kod tekrarını azaltır.
* Esneklik ve Genişletilebilirlik: Yeni özellikler eklemek veya mevcut olanları değiştirmek genellikle mevcut kodu bozmadan yapılabilir. Çok biçimlilik sayesinde, yeni türetilmiş sınıflar ekleyerek sisteme yeni davranışlar kazandırmak kolaylaşır.
* Daha İyi Tasarım ve Analiz: Gerçek dünya nesnelerini ve ilişkilerini modelleyerek, problemin daha sezgisel bir şekilde anlaşılmasına ve çözümün daha doğal bir şekilde tasarlanmasına yardımcı olur.
Dezavantajları:
* Karmaşıklık: Basit problemler için OOP'nin getirdiği soyutlama katmanları ve yapısal overhead gereksiz yere karmaşıklık yaratabilir.
* Performans Yükü: Özellikle sanal fonksiyon çağrıları gibi çalışma zamanı çok biçimliliği özellikleri, geleneksel fonksiyon çağrılarına göre hafif bir performans yükü getirebilir. Ancak modern derleyiciler ve donanımlar sayesinde bu fark genellikle ihmal edilebilir düzeydedir.
* Bellek Tüketimi: Her nesnenin kendi veri üyelerine sahip olması, çok sayıda küçük nesnenin bulunduğu durumlarda daha fazla bellek tüketimine yol açabilir.
SOLID Prensipleri: Daha İyi OOP Tasarımları İçin Rehber
Nesne Yönelimli Programlamanın temel prensiplerini anladıktan sonra, daha iyi ve sürdürülebilir tasarımlar yapmamıza yardımcı olan bir dizi ilke olan SOLID prensiplerine değinmek önemlidir. Robert C. Martin (Uncle Bob) tarafından popülerleştirilen bu prensipler, yazılımın daha anlaşılır, esnek ve bakımı kolay olmasını sağlamak için yol göstericidir.
* O - Open/Closed Principle (Açık/Kapalı Prensibi): Yazılım varlıkları (sınıflar, modüller, fonksiyonlar vb.) geliştirmeye açık, ancak değiştirmeye kapalı olmalıdır. Yeni özellikler eklemek için mevcut kodu değiştirmek yerine, yeni kod eklenmelidir.
* L - Liskov Substitution Principle (Liskov Yerine Geçme Prensibi): Temel sınıfın işaretçisi veya referansı aracılığıyla türetilmiş sınıf nesneleri kullanıldığında, programın doğruluğu bozulmamalıdır. Yani, türetilmiş sınıflar temel sınıflarının yerini sorunsuz bir şekilde alabilmelidir.
* I - Interface Segregation Principle (Arayüz Ayırma Prensibi): Büyük, genel amaçlı arayüzler yerine, istemcilere özgü, daha küçük ve odaklanmış arayüzler tercih edilmelidir. Bir sınıfın, kullanmadığı metotları uygulamaya zorlanmaması gerekir.
* D - Dependency Inversion Principle (Bağımlılık Tersine Çevirme Prensibi): Üst düzey modüller, alt düzey modüllere bağlı olmamalıdır; her ikisi de soyutlamalara bağlı olmalıdır. Soyutlamalar detaylara bağlı olmamalı, detaylar soyutlamalara bağlı olmalıdır.
Bu prensipler, C++ ile yazılan nesne yönelimli uygulamaların ölçeklenebilirliğini ve sürdürülebilirliğini artırmak için çok değerli bir çerçeve sunar.
Sonuç
C++'da Nesne Yönelimli Programlama, modern yazılım geliştirmenin ayrılmaz bir parçasıdır. Kapsülleme, kalıtım, çok biçimlilik ve soyutlama gibi temel prensipleri anlamak ve uygulamak, daha güçlü, esnek ve bakımı kolay yazılımlar oluşturmanızı sağlar. Sınıflar ve nesneler aracılığıyla gerçek dünya kavramlarını modellemek, kompleks problemleri yönetilebilir parçalara ayırmaya yardımcı olur.
Unutmayın ki, programlamada olduğu gibi OOP'de de en iyi öğrenme yöntemi pratik yapmaktır. Çeşitli senaryolar üzerinde sınıflar tasarlayarak, kalıtım hiyerarşileri kurarak ve çok biçimliliği kullanarak deneyim kazanın. Ayrıca, OOP tasarım desenlerini ve SOLID prensiplerini incelemek, kodunuzu daha da geliştirecek ve sizi daha yetkin bir yazılımcı yapacaktır.
Daha fazla bilgi ve pratik örnekler için aşağıdaki kaynağı ziyaret edebilirsiniz:
GeeksforGeeks C++ OOP Rehberi
Başarılar dileriz!
OOP, bir yazılım sistemini, birbirleriyle etkileşime giren nesneler topluluğu olarak görür. Her nesne, kendi verilerine (niteliklerine) ve bu veriler üzerinde işlem yapan davranışlara (metotlara) sahiptir. Bu yaklaşım, kodun daha modüler, anlaşılır, esnek ve yeniden kullanılabilir olmasını sağlar. Özellikle büyük ölçekli projelerde, OOP'nin sunduğu bu avantajlar, geliştirme sürecini önemli ölçüde hızlandırır ve hataların azalmasına yardımcı olur.
Bu kapsamlı rehberde, C++'da Nesne Yönelimli Programlamanın temel taşlarını ayrıntılı olarak inceleyeceğiz. Kavramsal açıklamaların yanı sıra, pratik kod örnekleri ve sıkça karşılaşılan senaryolar üzerinden konuları pekiştireceğiz. Amacımız, C++ ile sağlam, bakımı kolay ve ölçeklenebilir nesne yönelimli uygulamalar geliştirmek için size sağlam bir temel sunmaktır.
OOP'nin Temel Prensipleri (Dört Temel Taşı):
Nesne Yönelimli Programlamanın gücü, dört temel prensip üzerine kuruludur. Bu prensipler, yazılım tasarımında esneklik, sürdürülebilirlik ve yeniden kullanılabilirlik sağlamak için birlikte çalışır. Bu prensipler şunlardır:
- Kapsülleme (Encapsulation)
Kapsülleme, veri ve bu veriler üzerinde işlem yapan metotları tek bir birim (sınıf) içinde bir araya getirme ve bu birimin iç yapısını dış dünyadan gizleme prensibidir. Bir sınıfın içindeki veriler genellikle `private` (özel) olarak tanımlanır, böylece doğrudan dışarıdan erişilemezler. Bu verilere erişim ve onları değiştirme işlemleri, sınıf içinde tanımlanmış `public` (genel) metotlar (genellikle "getter" ve "setter" metotları) aracılığıyla yapılır. Bu sayede, sınıfın iç yapısında yapılan değişiklikler, dışarıdaki kodun çalışmasını etkilemez. Bu, yazılımın daha sağlam ve bakımı kolay olmasını sağlar.
Örnek olarak, bir `Hesap` sınıfının bakiye bilgisini düşünelim. Bakiye direkt değiştirilemez, ancak `paraYatir` veya `paraCek` gibi metotlar aracılığıyla güvenli bir şekilde güncellenebilir. Bu sayede negatif bakiye gibi istenmeyen durumlar engellenebilir.
Kod:class Hesap { private: double bakiye; // Kapsüllenmiş veri public: // Kurucu metot Hesap(double ilkBakiye) : bakiye(ilkBakiye) {} // Getter metot double getBakiye() const { return bakiye; } // Setter benzeri işlem metotları void paraYatir(double miktar) { if (miktar > 0) { bakiye += miktar; std::cout << miktar << " TL yatirildi. Yeni bakiye: " << bakiye << std::endl; } else { std::cout << "Gecersiz miktar." << std::endl; } } void paraCek(double miktar) { if (miktar > 0 && bakiye >= miktar) { bakiye -= miktar; std::cout << miktar << " TL cekildi. Yeni bakiye: " << bakiye << std::endl; } else { std::cout << "Yetersiz bakiye veya gecersiz miktar." << std::endl; } } };
- Kalıtım (Inheritance)
Kalıtım, mevcut bir sınıftan (temel sınıf veya üst sınıf) yeni bir sınıf (türetilmiş sınıf veya alt sınıf) oluşturma yeteneğidir. Türetilmiş sınıf, temel sınıfın niteliklerini (veri üyelerini) ve davranışlarını (metotlarını) miras alır ve üzerine kendi özgü niteliklerini/davranışlarını ekleyebilir veya mevcutları değiştirebilir. Bu, kod tekrarını önler ve hiyerarşik bir yapı oluşturarak kodun daha düzenli ve genişletilebilir olmasını sağlar. Örneğin, bir `Hayvan` sınıfından `Kedi` ve `Köpek` sınıfları türetebiliriz. `Hayvan` sınıfı `sesCikar()` metoduna sahipken, `Kedi` bunu `miyav()` olarak, `Köpek` ise `havla()` olarak özelleştirebilir.
C++'da kalıtım, `public`, `protected` veya `private` anahtar kelimeleriyle belirlenir. Genellikle `public` kalıtım kullanılır, bu da temel sınıfın `public` ve `protected` üyelerinin türetilmiş sınıfta aynı erişim seviyesini korumasını sağlar.
Kod:class TemelSekil { // Temel sınıf public: void enBoyAyarla(int e, int b) { en = e; boy = b; } protected: int en; int boy; }; class Dikdortgen : public TemelSekil { // Türetilmiş sınıf public: int alanHesapla() { return (en * boy); } }; class Ucgen : public TemelSekil { // Başka bir türetilmiş sınıf public: int alanHesapla() { return (en * boy) / 2; } };
- Çok Biçimlilik (Polymorphism)
Çok biçimlilik, bir nesnenin farklı formlarda davranabilme yeteneğidir. Yani, farklı sınıfların aynı isimde metotlara sahip olabilmesi ve bu metotların çağrıldığında nesnenin tipine göre farklı davranışlar sergilemesidir. C++'da çok biçimlilik iki ana şekilde gerçekleşir:
* Derleme Zamanı Çok Biçimliliği (Compile-time Polymorphism): Bu, fonksiyon aşırı yüklemesi (function overloading) ve operatör aşırı yüklemesi (operator overloading) ile elde edilir. Farklı parametre listelerine sahip aynı isimdeki fonksiyonlar veya farklı anlamlar yüklenmiş operatörler sayesinde derleme zamanında hangi fonksiyonun/operatörün çağrılacağı belirlenir.
* Çalışma Zamanı Çok Biçimliliği (Run-time Polymorphism): Bu, sanal fonksiyonlar (virtual functions) ve soyut sınıflar (abstract classes) aracılığıyla elde edilir. Bir temel sınıf işaretçisi veya referansı aracılığıyla türetilmiş sınıf nesnelerine erişildiğinde, çağrılan metodun hangi versiyonunun çalışacağına çalışma zamanında karar verilir. Bu, özellikle temel sınıf tipinde nesnelerin bir koleksiyonunu işlerken ve her nesnenin kendi tipine özgü davranış sergilemesini istediğinizde çok güçlüdür.
Çalışma zamanı çok biçimliliği, özellikle kalıtım hiyerarşilerinde esneklik sağlar.
Kod:class Hayvan { public: virtual void sesCikar() const { // Sanal fonksiyon std::cout << "Hayvan ses cikardi." << std::endl; } }; class Kedi : public Hayvan { public: void sesCikar() const override { // Override keyword C++11 sonrası tavsiye edilir std::cout << "Miyav!" << std::endl; } }; class Kopek : public Hayvan { public: void sesCikar() const override { std::cout << "Hav hav!" << std::endl; } }; // Ana fonksiyon içinde kullanım // Hayvan* hayvanPtr1 = new Kedi(); // Hayvan* hayvanPtr2 = new Kopek(); // hayvanPtr1->sesCikar(); // Miyav! // hayvanPtr2->sesCikar(); // Hav hav!
- Soyutlama (Abstraction)
Soyutlama, bir sistemin karmaşıklığını yönetmek için sadece gerekli detayları gösterme ve gereksiz detayları gizleme prensibidir. Kullanıcının sadece bilmesi gerekenlere odaklanmasını sağlar. Örneğin, bir arabayı kullanırken motorun iç işleyişini bilmemize gerek yoktur; sadece direksiyon, gaz ve fren pedalları gibi arayüzlere ihtiyacımız vardır.
C++'da soyutlama, soyut sınıflar ve saf sanal fonksiyonlar aracılığıyla uygulanır. Bir sınıf, en az bir saf sanal fonksiyona sahipse soyut bir sınıf haline gelir ve doğrudan nesnesi oluşturulamaz. Bu sınıflar, bir "arayüz" veya bir "sözleşme" görevi görür; ondan türetilen sınıflar, soyut fonksiyonları uygulamak zorundadır.
Kod:class Cizilebilir { // Soyut sınıf (arayüz gibi) public: // Saf sanal fonksiyon virtual void ciz() = 0; // Sanal yıkıcı, kalıtımda önemlidir virtual ~Cizilebilir() {} }; class Daire : public Cizilebilir { public: void ciz() override { std::cout << "Daire ciziliyor." << std::endl; } }; class Kare : public Cizilebilir { public: void ciz() override { std::cout << "Kare ciziliyor." << std::endl; } }; // Cizilebilir* sekil1 = new Daire(); // Cizilebilir* sekil2 = new Kare(); // sekil1->ciz(); // Daire ciziliyor. // sekil2->ciz(); // Kare ciziliyor.
Sınıflar ve Nesneler: OOP'nin Yapı Taşları
OOP'nin temel yapı taşları sınıflar ve nesnelerdir.
* Sınıf (Class): Bir sınıf, belirli bir türdeki nesnelerin şablonu veya blueprint'idir. Nesnelerin sahip olacağı özellikleri (veri üyeleri/attributes) ve yapabileceği eylemleri (metotlar/methods) tanımlar. Sınıflar, kendi başlarına bellek üzerinde yer kaplamazlar; sadece nesne oluşturmak için bir kılavuz sağlarlar. Örneğin, `Araba` bir sınıftır.
* Nesne (Object): Bir sınıfın gerçek bir örneğidir. Bellekte yer kaplar ve sınıfın tanımladığı özelliklere belirli değerler atanmış ve davranışlara sahip somut bir varlıktır. Örneğin, "benim arabam" veya "komşunun arabası" birer `Araba` nesnesidir. Her nesne, sınıfının bir örneğidir ve kendi veri üyelerinin kopyalarına sahiptir.
Kod:
// Bir sınıf tanımı
class Araba {
private: // Özel erişim: Sadece sınıf içinden erişilebilir
std::string marka;
std::string model;
int yil;
int mevcutHiz;
public: // Genel erişim: Sınıf dışından erişilebilir
// Kurucu metot: Nesne oluşturulduğunda çağrılır
Araba(std::string m, std::string mod, int y) : marka(m), model(mod), yil(y), mevcutHiz(0) {
std::cout << marka << " " << model << " olusturuldu." << std::endl;
}
// Metotlar (davranışlar)
void gazaBas(int miktar) {
mevcutHiz += miktar;
std::cout << "Hiz " << miktar << " arttirildi. Yeni hiz: " << mevcutHiz << std::endl;
}
void frenYap(int miktar) {
mevcutHiz -= miktar;
if (mevcutHiz < 0) mevcutHiz = 0;
std::cout << "Hiz " << miktar << " azaltildi. Yeni hiz: " << mevcutHiz << std::endl;
}
// Getter metotları (kapsüllemeyi destekler)
std::string getMarka() const { return marka; }
std::string getModel() const { return model; }
int getYil() const { return yil; }
int getMevcutHiz() const { return mevcutHiz; }
};
// Ana fonksiyonda nesne oluşturma ve kullanma
/*
int main() {
Araba audi("Audi", "A4", 2020); // audi nesnesi oluşturuldu
audi.gazaBas(50);
audi.frenYap(10);
std::cout << "Marka: " << audi.getMarka() << ", Model: " << audi.getModel() << std::endl;
return 0;
}
*/
Kurucular (Constructors) ve Yıkıcılar (Destructors): Nesne Yaşam Döngüsü
Her nesnenin bir yaşam döngüsü vardır: oluşturulur, kullanılır ve yok edilir. C++'da bu döngü kurucular ve yıkıcılar tarafından yönetilir.
* Kurucu (Constructor): Bir sınıfın nesnesi oluşturulduğunda otomatik olarak çağrılan özel bir metottur. Amacı, nesnenin veri üyelerini başlatmak ve nesneyi kullanılabilir bir duruma getirmektir. Kurucunun dönüş tipi olmaz ve sınıf adıyla aynı ada sahiptir. Birden fazla kurucu tanımlanabilir (kurucu aşırı yüklemesi), bu da nesnelerin farklı yollarla başlatılmasına olanak tanır.
* Varsayılan Kurucu (Default Constructor): Parametre almayan kurucudur. Eğer siz bir kurucu tanımlamazsanız, C++ derleyicisi sizin için boş bir varsayılan kurucu oluşturur.
* Parametreli Kurucu (Parameterized Constructor): Parametre alan kurucudur. Nesne oluşturulurken başlangıç değerleri sağlamak için kullanılır.
* Kopyalama Kurucusu (Copy Constructor): Var olan bir nesneden yeni bir nesne oluşturmak için kullanılır.
* Yıkıcı (Destructor): Bir sınıfın nesnesi kapsam dışına çıktığında veya `delete` operatörü ile bellekten silindiğinde otomatik olarak çağrılan özel bir metottur. Amacı, nesnenin kullandığı kaynakları (bellek, dosya tanıtıcıları, ağ bağlantıları vb.) serbest bırakmaktır. Yıkıcının da dönüş tipi olmaz, sınıf adıyla aynı ada sahiptir ancak önüne bir tilde (~) işareti eklenir ve parametre almaz. Bir sınıfta sadece bir yıkıcı olabilir. Kalıtım hiyerarşilerinde sanal yıkıcıların (virtual destructors) kullanılması, türetilmiş sınıf nesnelerinin doğru şekilde yok edilmesi için kritik öneme sahiptir. Aksi takdirde, temel sınıf işaretçisi üzerinden türetilmiş sınıf nesnesi silindiğinde sadece temel sınıfın yıkıcısı çağrılabilir ve bu da bellek sızıntılarına yol açabilir.
Erişim Belirleyiciler (Access Specifiers): Veri Güvenliği
C++'da sınıfların üyelerine (veri ve metotlar) dışarıdan nasıl erişileceğini belirlemek için üç ana erişim belirleyici kullanılır:
* public: Bu erişim belirleyiciye sahip üyeler, sınıf içinden ve dışından, yani herkes tarafından erişilebilir. Bir sınıfın arayüzünü tanımlayan metotlar ve veriler genellikle `public` olarak işaretlenir. Örneğin, bir `Araba` nesnesinin `gazaBas()` metodu `public` olmalıdır, çünkü kullanıcı bu metodu çağırarak arabayı hızlandırmak ister.
* private: Bu erişim belirleyiciye sahip üyeler, sadece sınıfın kendi metotları tarafından erişilebilir. Dışarıdan doğrudan erişilemezler. Kapsülleme prensibini uygulamak için kullanılırlar. Bir sınıfın dahili çalışma detaylarını ve hassas verilerini korumak için idealdir. Örneğin, `Araba` sınıfının `mevcutHiz` değişkeni `private` olmalıdır; dışarıdan direkt değiştirilememeli, sadece `gazaBas()` veya `frenYap()` gibi kontrollü metotlar aracılığıyla güncellenmelidir.
* protected: Bu erişim belirleyiciye sahip üyeler, sınıfın kendi metotları ve o sınıftan türetilmiş sınıfların metotları tarafından erişilebilir. `private` ile `public` arasında bir erişim seviyesi sunar. Özellikle kalıtım kullanılırken, temel sınıfın belirli üyelerinin türetilmiş sınıflar tarafından erişilebilmesini ancak dışarıdan gizli kalmasını sağlamak için kullanışlıdır.
İleri OOP Konuları ve Tasarım Prensipleri
* Saf Sanal Fonksiyonlar ve Soyut Sınıflar: Daha önce soyutlama bölümünde de bahsettiğimiz gibi, C++'da saf sanal fonksiyonlar (`= 0` ile işaretlenir) ve bunları içeren sınıflar (soyut sınıflar), arayüz tanımlamak için kullanılır. Bir sınıfın, alt sınıfları tarafından mutlaka uygulanması gereken bir davranışı olduğunu belirtmenin en güçlü yoludur. Soyut sınıflar, genellikle bir kalıtım hiyerarşisinin başında yer alır ve genel bir konsepti temsil eder. Onlardan türetilen somut sınıflar, bu soyut kavramı kendi özgün şekillerinde gerçekleştirir. Örneğin, `GeometrikSekil` soyut bir sınıf olabilir ve `alanHesapla()` adında saf sanal bir metoda sahip olabilir. `Daire` ve `Kare` gibi somut sınıflar bu metodu kendi şekillerine göre uygulamak zorundadır.
* Sanal Yıkıcılar: Kalıtım ve çok biçimlilikle çalışırken, temel sınıf işaretçisi üzerinden türetilmiş sınıf nesnesini `delete` ettiğinizde, türetilmiş sınıfın yıkıcısının doğru bir şekilde çağrılmasını sağlamak için temel sınıftaki yıkıcının `virtual` olarak tanımlanması zorunludur. Aksi takdirde, sadece temel sınıfın yıkıcısı çağrılır ve bu da türetilmiş sınıfın dinamik olarak ayırdığı kaynakların serbest bırakılmamasına ve bellek sızıntılarına yol açabilir. Bu, C++'da sıkça yapılan bir hatadır ve önüne geçmek için önemli bir kuraldır.
Örnek Uygulama Senaryosu: Hayvan Hiyerarşisi
Çok biçimlilik ve kalıtımı daha iyi anlamak için popüler bir örnek olan hayvan hiyerarşisini inceleyelim.

Bu görsel, bir `Hayvan` temel sınıfından `Kedi` ve `Köpek` gibi türetilmiş sınıfların nasıl oluşturulabileceğini görselleştirebilir. Her hayvanın genel bir `sesCikar()` davranışı vardır, ancak her alt sınıf bu davranışı kendine özgü bir şekilde uygular. Bir hayvan listesini dönerken, her hayvanın kendi sesini çıkarmasını sağlamak için çalışma zamanı çok biçimliliğinden faydalanırız.
Faydaları ve Dezavantajları
Her programlama paradigması gibi, OOP'nin de kendine göre avantajları ve dezavantajları vardır.
Faydaları:
* Kodun Yönetilebilirliği ve Modülerlik: Kod, bağımsız ve ilişkili birimler (nesneler) halinde düzenlenir. Bu, büyük projelerin daha kolay yönetilmesini ve her bir parçanın ayrı ayrı geliştirilip test edilmesini sağlar.
* Yeniden Kullanılabilirlik: Sınıflar bir kez yazıldıktan sonra, farklı projelerde veya aynı projenin farklı yerlerinde tekrar tekrar kullanılabilir. Kalıtım da kod tekrarını azaltır.
* Esneklik ve Genişletilebilirlik: Yeni özellikler eklemek veya mevcut olanları değiştirmek genellikle mevcut kodu bozmadan yapılabilir. Çok biçimlilik sayesinde, yeni türetilmiş sınıflar ekleyerek sisteme yeni davranışlar kazandırmak kolaylaşır.
* Daha İyi Tasarım ve Analiz: Gerçek dünya nesnelerini ve ilişkilerini modelleyerek, problemin daha sezgisel bir şekilde anlaşılmasına ve çözümün daha doğal bir şekilde tasarlanmasına yardımcı olur.
Dezavantajları:
* Karmaşıklık: Basit problemler için OOP'nin getirdiği soyutlama katmanları ve yapısal overhead gereksiz yere karmaşıklık yaratabilir.
* Performans Yükü: Özellikle sanal fonksiyon çağrıları gibi çalışma zamanı çok biçimliliği özellikleri, geleneksel fonksiyon çağrılarına göre hafif bir performans yükü getirebilir. Ancak modern derleyiciler ve donanımlar sayesinde bu fark genellikle ihmal edilebilir düzeydedir.
* Bellek Tüketimi: Her nesnenin kendi veri üyelerine sahip olması, çok sayıda küçük nesnenin bulunduğu durumlarda daha fazla bellek tüketimine yol açabilir.
SOLID Prensipleri: Daha İyi OOP Tasarımları İçin Rehber
Nesne Yönelimli Programlamanın temel prensiplerini anladıktan sonra, daha iyi ve sürdürülebilir tasarımlar yapmamıza yardımcı olan bir dizi ilke olan SOLID prensiplerine değinmek önemlidir. Robert C. Martin (Uncle Bob) tarafından popülerleştirilen bu prensipler, yazılımın daha anlaşılır, esnek ve bakımı kolay olmasını sağlamak için yol göstericidir.
* S - Single Responsibility Principle (Tek Sorumluluk Prensibi): Bir sınıfın veya modülün yalnızca tek bir sorumluluğu olmalıdır, yani değiştirme için yalnızca tek bir nedeni olmalıdır.SOLID prensipleri, Nesne Yönelimli Tasarımda daha anlaşılır, esnek ve bakımı kolay sistemler oluşturmak için bir dizi ilkedir.
* O - Open/Closed Principle (Açık/Kapalı Prensibi): Yazılım varlıkları (sınıflar, modüller, fonksiyonlar vb.) geliştirmeye açık, ancak değiştirmeye kapalı olmalıdır. Yeni özellikler eklemek için mevcut kodu değiştirmek yerine, yeni kod eklenmelidir.
* L - Liskov Substitution Principle (Liskov Yerine Geçme Prensibi): Temel sınıfın işaretçisi veya referansı aracılığıyla türetilmiş sınıf nesneleri kullanıldığında, programın doğruluğu bozulmamalıdır. Yani, türetilmiş sınıflar temel sınıflarının yerini sorunsuz bir şekilde alabilmelidir.
* I - Interface Segregation Principle (Arayüz Ayırma Prensibi): Büyük, genel amaçlı arayüzler yerine, istemcilere özgü, daha küçük ve odaklanmış arayüzler tercih edilmelidir. Bir sınıfın, kullanmadığı metotları uygulamaya zorlanmaması gerekir.
* D - Dependency Inversion Principle (Bağımlılık Tersine Çevirme Prensibi): Üst düzey modüller, alt düzey modüllere bağlı olmamalıdır; her ikisi de soyutlamalara bağlı olmalıdır. Soyutlamalar detaylara bağlı olmamalı, detaylar soyutlamalara bağlı olmalıdır.
Bu prensipler, C++ ile yazılan nesne yönelimli uygulamaların ölçeklenebilirliğini ve sürdürülebilirliğini artırmak için çok değerli bir çerçeve sunar.
Sonuç
C++'da Nesne Yönelimli Programlama, modern yazılım geliştirmenin ayrılmaz bir parçasıdır. Kapsülleme, kalıtım, çok biçimlilik ve soyutlama gibi temel prensipleri anlamak ve uygulamak, daha güçlü, esnek ve bakımı kolay yazılımlar oluşturmanızı sağlar. Sınıflar ve nesneler aracılığıyla gerçek dünya kavramlarını modellemek, kompleks problemleri yönetilebilir parçalara ayırmaya yardımcı olur.
Unutmayın ki, programlamada olduğu gibi OOP'de de en iyi öğrenme yöntemi pratik yapmaktır. Çeşitli senaryolar üzerinde sınıflar tasarlayarak, kalıtım hiyerarşileri kurarak ve çok biçimliliği kullanarak deneyim kazanın. Ayrıca, OOP tasarım desenlerini ve SOLID prensiplerini incelemek, kodunuzu daha da geliştirecek ve sizi daha yetkin bir yazılımcı yapacaktır.
Daha fazla bilgi ve pratik örnekler için aşağıdaki kaynağı ziyaret edebilirsiniz:
GeeksforGeeks C++ OOP Rehberi
Başarılar dileriz!