← Kursa Dön
📄 Text · 15 min

Sınıf ve Nesne Kavramı

Şimdiye kadar fonksiyonlar, döngüler, diziler gibi araçlarla "prosedürel" (adım adım) programlama yaptık. Ama gerçek dünya böyle çalışmıyor. Gerçek dünyada nesneler var — arabalar, öğrenciler, banka hesapları. Her nesnenin özellikleri (renk, isim, bakiye) ve davranışları (sür, sınava gir, para çek) var.

Nesne Yönelimli Programlama (Object-Oriented Programming — OOP) bu gerçek dünya modellemesini koda taşır. Bu derste OOP'nin temel taşları olan sınıf (class) ve nesne (object) kavramlarını öğreneceğiz.


Sınıf Nedir?

Sınıfı bir çerez kalıbı gibi düşün. Kalıbın kendisi çerez değildir — ama kalıpla istediğin kadar çerez (nesne) üretebilirsin. Her çerez aynı şekle sahip ama farklı süslemeler (değerler) alabilir.

  • Sınıf (class) = Kalıp, şablon, plan

  • Nesne (object) = Kalıptan üretilen somut şey

Bir Araba sınıfı tanımladığında "her arabanın markası, rengi ve hızı olacak" demiş olursun. Sonra bu sınıftan "kırmızı Toyota" ve "mavi BMW" gibi somut nesneler üretirsin.


İlk Sınıfımız

#include <iostream>
#include <string>
using namespace std;

class Ogrenci {
public:
    // Üye değişkenler (member variables / attributes)
    string isim;
    int numara;
    double not_ortalamasi;

    // Üye fonksiyon (member function / method)
    void bilgi_yazdir() {
        cout << "İsim: " << isim << endl;
        cout << "Numara: " << numara << endl;
        cout << "Ortalama: " << not_ortalamasi << endl;
    }

    bool gecti_mi() {
        return not_ortalamasi >= 50.0;
    }
};  // Noktalı virgül unutma!

int main() {
    // Nesne oluşturma
    Ogrenci ali;
    ali.isim = "Ali Yılmaz";
    ali.numara = 1001;
    ali.not_ortalamasi = 78.5;

    ali.bilgi_yazdir();
    cout << "Geçti mi: " << (ali.gecti_mi() ? "Evet" : "Hayır") << endl;

    // İkinci nesne
    Ogrenci veli;
    veli.isim = "Veli Demir";
    veli.numara = 1002;
    veli.not_ortalamasi = 45.0;

    veli.bilgi_yazdir();
    cout << "Geçti mi: " << (veli.gecti_mi() ? "Evet" : "Hayır") << endl;

    return 0;
}

Her Ogrenci nesnesi kendi isim, numara, not_ortalamasi değerlerine sahip. ali.isim ile veli.isim farklı bellekte yaşar. Ama bilgi_yazdir() fonksiyonu ortaktır — hangi nesne üzerinden çağırılırsa onun verilerini kullanır.

💡 İpucu: Sınıf tanımının sonundaki noktalı virgülü (;) unutmak klasik bir C++ hatasıdır. Derleyici hata mesajı genellikle kafa karıştırıcı olur.


class vs struct Farkı

C++'ta class ve struct neredeyse aynı şey. Tek fark varsayılan erişim belirleyicisi:

  • class → üyeler varsayılan olarak private

  • struct → üyeler varsayılan olarak public

#include <iostream>
using namespace std;

struct Nokta {
    double x;  // varsayılan: public
    double y;
};

class Daire {
    double yaricap;  // varsayılan: private — dışarıdan erişilemez!
public:
    void ayarla(double r) { yaricap = r; }
    double alan() { return 3.14159 * yaricap * yaricap; }
};

int main() {
    Nokta p;
    p.x = 3.0;  // OK — public
    p.y = 4.0;

    Daire d;
    // d.yaricap = 5.0;  // HATA — private!
    d.ayarla(5.0);
    cout << "Alan: " << d.alan() << endl;

    return 0;
}

Genel kural:

  • Sadece veri tutan basit yapılar için struct kullan (DTO, POD)

  • Davranış ve kapsülleme gerektiren yapılar için class kullan

Teknik olarak birbirlerinin yerine kullanılabilir ama bu konvansiyon C++ topluluğunda yaygın.


Üye Değişkenler ve Üye Fonksiyonlar

Üye Değişkenler (Data Members)

Nesnenin "ne bildiğini" tanımlar — durumu (state):

class BankaHesabi {
public:
    string sahip;
    string hesap_no;
    double bakiye;
};

Üye Fonksiyonlar (Member Functions / Methods)

Nesnenin "ne yapabildiğini" tanımlar — davranışı (behavior):

#include <iostream>
#include <string>
using namespace std;

class BankaHesabi {
public:
    string sahip;
    string hesap_no;
    double bakiye = 0.0;  // varsayılan değer (C++11)

    void para_yatir(double miktar) {
        if (miktar > 0) {
            bakiye += miktar;
            cout << miktar << " TL yatırıldı. Bakiye: " << bakiye << endl;
        }
    }

    bool para_cek(double miktar) {
        if (miktar > 0 && miktar <= bakiye) {
            bakiye -= miktar;
            cout << miktar << " TL çekildi. Bakiye: " << bakiye << endl;
            return true;
        }
        cout << "Yetersiz bakiye!" << endl;
        return false;
    }

    void bilgi() {
        cout << sahip << " (" << hesap_no << "): " << bakiye << " TL" << endl;
    }
};

int main() {
    BankaHesabi hesap;
    hesap.sahip = "Ali Yılmaz";
    hesap.hesap_no = "TR12345";

    hesap.para_yatir(1000);   // 1000 TL yatırıldı. Bakiye: 1000
    hesap.para_cek(300);      // 300 TL çekildi. Bakiye: 700
    hesap.para_cek(800);      // Yetersiz bakiye!
    hesap.bilgi();            // Ali Yılmaz (TR12345): 700 TL

    return 0;
}

Nesne Oluşturma: Stack ve Heap

Stack'te Nesne (En Yaygın)

int main() {
    Ogrenci ali;  // stack'te oluşturuldu
    ali.isim = "Ali";

    // Scope sona erince otomatik yok edilir
    // Bellek yönetimi gerekmez
}

Stack'teki nesneler fonksiyon bitince otomatik olarak yok edilir. Bellek sızıntısı (memory leak) riski yok.

Heap'te Nesne (new / delete)

#include <iostream>
#include <string>
using namespace std;

class Ogrenci {
public:
    string isim;
    int numara;

    void yazdir() {
        cout << isim << " (" << numara << ")" << endl;
    }
};

int main() {
    // Heap'te oluştur — new ile
    Ogrenci* ptr = new Ogrenci();
    ptr->isim = "Ali";      // pointer ile erişirken -> kullan
    ptr->numara = 1001;
    ptr->yazdir();

    delete ptr;  // Belleği serbest bırak — UNUTMA!
    ptr = nullptr;

    return 0;
}

Heap'teki nesnelere pointer üzerinden erişirsin. . yerine -> operatörü kullanırsın.

Smart Pointer ile (Modern Yol)

#include <iostream>
#include <string>
#include <memory>
using namespace std;

class Ogrenci {
public:
    string isim;
    int numara;

    void yazdir() {
        cout << isim << " (" << numara << ")" << endl;
    }
};

int main() {
    // unique_ptr — otomatik bellek yönetimi
    auto ogrenci = make_unique<Ogrenci>();
    ogrenci->isim = "Ali";
    ogrenci->numara = 1001;
    ogrenci->yazdir();

    // Scope bitince otomatik delete edilir — sızıntı yok!
    return 0;
}

⚠️ Dikkat: new kullandıysan delete de kullanmalısın. Yoksa bellek sızıntısı olur. Modern C++'ta make_unique / make_shared tercih et — bellek yönetimini otomatikleştirir.


Header ve Source Dosyasına Ayırma

Küçük projelerde her şeyi tek dosyaya yazmak sorun olmaz. Ama proje büyüdükçe sınıfları ayrı dosyalara bölmek gerekir. C++'ta bunun standart yolu:

  • Header dosyası (.h veya .hpp): Sınıf bildirimi (declaration) — ne var, ne yapılabilir

  • Source dosyası (.cpp): Fonksiyonların tanımı (definition) — nasıl yapılır

Örnek Proje Yapısı

Ogrenci.h — Header dosyası:

#ifndef OGRENCI_H
#define OGRENCI_H

#include <string>

class Ogrenci {
public:
    std::string isim;
    int numara;
    double not_ortalamasi;

    // Fonksiyon bildirimleri (declaration)
    void bilgi_yazdir();
    bool gecti_mi();
    void not_guncelle(double yeni_not);
};

#endif // OGRENCI_H

Ogrenci.cpp — Source dosyası:

#include "Ogrenci.h"
#include <iostream>

// :: (scope resolution operator) ile sınıf adını belirt
void Ogrenci::bilgi_yazdir() {
    std::cout << "İsim: " << isim << std::endl;
    std::cout << "Numara: " << numara << std::endl;
    std::cout << "Ortalama: " << not_ortalamasi << std::endl;
}

bool Ogrenci::gecti_mi() {
    return not_ortalamasi >= 50.0;
}

void Ogrenci::not_guncelle(double yeni_not) {
    if (yeni_not >= 0 && yeni_not <= 100) {
        not_ortalamasi = yeni_not;
    }
}

main.cpp — Ana dosya:

#include "Ogrenci.h"
#include <iostream>

int main() {
    Ogrenci ali;
    ali.isim = "Ali Yılmaz";
    ali.numara = 1001;
    ali.not_ortalamasi = 78.5;

    ali.bilgi_yazdir();
    return 0;
}

Header Guard

#ifndef / #define / #endif bloğuna header guard denir. Aynı header'ın birden fazla kez dahil edilmesini engeller. Modern alternatif:

#pragma once  // Çoğu derleyicide çalışır, daha kısa

#include <string>

class Ogrenci {
    // ...
};

:: (Scope Resolution Operator)

Source dosyasında Ogrenci::bilgi_yazdir() yazarken kullandığımız :: operatörü, fonksiyonun hangi sınıfa ait olduğunu belirtir. "Bu fonksiyon Ogrenci sınıfının bilgi_yazdir'ıdır" demek.

💡 İpucu: Küçük yardımcı fonksiyonlar doğrudan header'da tanımlanabilir (inline olur). Ama büyük fonksiyonları .cpp dosyasına taşı — derleme süresi kısalır ve bağımlılıklar azalır.


Sınıf İçinde Sınıf Kullanımı (Composition)

Sınıflar başka sınıfları üye değişken olarak içerebilir. Buna composition (bileşim) denir:

#include <iostream>
#include <string>
using namespace std;

class Adres {
public:
    string sehir;
    string ilce;
    string sokak;

    void yazdir() {
        cout << sokak << ", " << ilce << "/" << sehir << endl;
    }
};

class Ogrenci {
public:
    string isim;
    int numara;
    Adres adres;  // Composition — Ogrenci bir Adres'e sahip

    void bilgi() {
        cout << isim << " (" << numara << ")" << endl;
        cout << "Adres: ";
        adres.yazdir();
    }
};

int main() {
    Ogrenci ali;
    ali.isim = "Ali Yılmaz";
    ali.numara = 1001;
    ali.adres.sehir = "İstanbul";
    ali.adres.ilce = "Kadıköy";
    ali.adres.sokak = "Moda Cd. No:5";

    ali.bilgi();

    return 0;
}

Bu "has-a" (sahiplik) ilişkisidir: "Öğrenci bir adrese sahiptir." İleride göreceğimiz kalıtım (inheritance) ise "is-a" (olma) ilişkisidir.


Sınıf Kullanırken Sık Yapılan Hatalar

1. Noktalı Virgül Unutma

class Ogrenci {
    // ...
}  // HATA! Sonunda ; olmalı

class Ogrenci {
    // ...
};  // Doğru

2. Stack ve Heap Erişim Karışıklığı

Ogrenci ali;          // stack
ali.isim = "Ali";     // . ile eriş

Ogrenci* ptr = new Ogrenci();  // heap
ptr->isim = "Ali";   // -> ile eriş
// ptr.isim = "Ali";  // HATA!

3. Header Guard Unutma

Aynı header birden fazla dosyadan dahil edilirse, header guard olmadan çoklu tanımlama (multiple definition) hatası alırsın.


Özet

  • Sınıf (class) bir şablon/kalıptır; nesne (object) bu şablondan üretilen somut örnektir. Çerez kalıbı ve çerezler gibi düşün.

  • class ve struct arasındaki tek fark varsayılan erişim belirleyicisidir: class → private, struct → public.

  • Sınıflar üye değişkenler (veri/durum) ve üye fonksiyonlar (davranış) içerir. Nesnenin ne bildiğini ve ne yapabildiğini tanımlar.

  • Nesneler stack'te (otomatik yönetim) veya heap'te (new/delete veya smart pointer) oluşturulabilir. Stack tercih edilir.

  • Büyük projelerde sınıf bildirimi header dosyasına (.h), fonksiyon tanımları source dosyasına (.cpp) ayrılır. Header guard veya #pragma once kullanılır.

  • Composition ile bir sınıf başka sınıfları üye olarak içerebilir — "has-a" ilişkisi.