← Kursa Dön
📄 Text · 12 min

Tür Dönüşümü (Type Casting)

Bir kutudan başka bir kutuya eşya aktarmaya benzetebilirsin tür dönüşümünü. Büyük bir kutudaki eşyayı küçük bir kutuya koymaya çalışırsan, sığmayan kısım kaybolur. Küçük kutudaki eşyayı büyük kutuya koyarsan sorun olmaz — ama kutu gereksiz yere büyük kalır. C++'ta veriler de tipler arasında geçiş yaparken benzer durumlar yaşar.

Bu derste otomatik dönüşümlerden C++'ın güçlü cast mekanizmalarına kadar tür dönüşümünün tüm yönlerini öğreneceksin.


Implicit (Otomatik) Tür Dönüşümü

C++ bazı tür dönüşümlerini sen istemeden otomatik yapar. Buna implicit conversion (örtük dönüşüm) denir:

#include <iostream>

int main() {
    int tam = 42;
    double ondalik = tam;  // int -> double, otomatik ve guvenli
    std::cout << "double: " << ondalik << std::endl;  // 42.0

    double pi = 3.14159;
    int kesilmis = pi;  // double -> int, veri kaybi! Ondalik kisim gider
    std::cout << "int: " << kesilmis << std::endl;  // 3

    char harf = 'A';
    int ascii = harf;  // char -> int, otomatik
    std::cout << "ASCII: " << ascii << std::endl;  // 65

    bool bayrak = 42;  // int -> bool, 0 olmayan her sey true
    std::cout << std::boolalpha << "bool: " << bayrak << std::endl;  // true

    return 0;
}

Dönüşüm Hiyerarşisi (Promotion Rules)

Farklı tipteki değerler bir ifadede karşılaşınca, C++ küçük tipi büyüğe otomatik yükseltir:

bool → char → short → int → long → long long → float → double → long double

Yani int + double işleminde int önce double'a dönüştürülür, sonuç double olur:

#include <iostream>

int main() {
    int a = 5;
    double b = 2.5;

    auto sonuc = a + b;  // int + double = double
    std::cout << "5 + 2.5 = " << sonuc << std::endl;  // 7.5
    std::cout << "Tip boyutu: " << sizeof(sonuc) << std::endl;  // 8 (double)

    // Dikkat: int / int = int (ikisi de ayni tipte, promotion yok)
    auto bolum = 7 / 2;  // int / int = int = 3
    std::cout << "7 / 2 = " << bolum << std::endl;

    return 0;
}

💡 İpucu: Otomatik dönüşüm "güvenli" yönde (küçükten büyüğe) yapıldığında sorun çıkarmaz. Ama büyükten küçüğe (double → int) yapıldığında veri kaybı olur. Derleyici genelde uyarı verir — bu uyarıları ciddiye al.


Narrowing Conversion — Daraltıcı Dönüşüm

Veri kaybına yol açabilecek dönüşümlere "narrowing conversion" denir. Süslü parantez ile başlatma {} bu tür dönüşümleri engeller:

#include <iostream>

int main() {
    // = ile — derler, sadece uyari verir
    int a = 3.14;      // 3 — ondalik kayboldu
    char b = 1000;     // char araligi disinda — tanimsiz

    // {} ile — DERLEME HATASI verir
    // int c{3.14};     // HATA! Narrowing conversion
    // char d{1000};    // HATA! char araligini asar

    // Guvenli donusumler {} ile sorunsuz calisir
    int e{42};          // OK
    double f{42};       // OK — int -> double guvenli

    std::cout << "a: " << a << std::endl;

    return 0;
}

⚠️ Dikkat: Bu, {} ile başlatmanın en büyük avantajıdır. int x = 3.14; sessizce veri kaybeder, ama int x{3.14}; derleme hatası verir. Hataları derleme zamanında yakalamanın çalışma zamanında yakalamaktan çok daha iyi olduğunu unutma.


C-Style Cast — Eski ve Tehlikeli

C dilinden kalma cast söz dizimi hâlâ C++'ta çalışır ama kullanılması önerilmez:

#include <iostream>

int main() {
    double pi = 3.14159;

    // C-style cast
    int tam = (int)pi;
    std::cout << "C-style: " << tam << std::endl;  // 3

    // Fonksiyon tarzı cast (ayni sey)
    int tam2 = int(pi);
    std::cout << "Fonksiyon: " << tam2 << std::endl;  // 3

    return 0;
}

Neden Tehlikeli?

C-style cast çok güçlüdür — neredeyse her dönüşümü sessizce yapar. Sorun da burada: hangi tür dönüşüm yapıldığını anlamak zor ve tehlikeli dönüşümleri bile hatasız derler.

C-style cast arka planda sırayla şunları dener:

  1. const_cast

  2. static_cast

  3. static_cast + const_cast

  4. reinterpret_cast

  5. reinterpret_cast + const_cast

Yani (int*)pointer yazdığında, derleyici bu beş yoldan hangisini kullandığını bilmezsin. Bu da hata ayıklamayı çok zorlaştırır.

⚠️ Dikkat: Yeni C++ kodunda C-style cast kullanma. Her zaman C++ cast operatörlerini (static_cast, dynamic_cast vb.) kullan. Bunlar hem niyetini açıkça belirtir hem de derleyicinin seni koruyabilmesini sağlar.


static_cast — En Yaygın ve Güvenli Cast

static_cast derleme zamanında yapılan, en yaygın kullanılan cast türüdür. İlişkili tipler arasında dönüşüm yapar:

#include <iostream>

int main() {
    // Sayisal donusumler
    double pi = 3.14159;
    int tam = static_cast<int>(pi);
    std::cout << "double -> int: " << tam << std::endl;  // 3

    // Tam sayi bolmede ondalikli sonuc elde etme
    int a = 7, b = 2;
    double sonuc = static_cast<double>(a) / b;
    std::cout << "7 / 2 = " << sonuc << std::endl;  // 3.5

    // char <-> int donusumu
    char harf = 'A';
    int ascii = static_cast<int>(harf);
    std::cout << "'A' = " << ascii << std::endl;  // 65

    char geri = static_cast<char>(66);
    std::cout << "66 = '" << geri << "'" << std::endl;  // 'B'

    return 0;
}

static_cast ve enum

static_cast enum (numaralandırma) tipleriyle de çalışır:

#include <iostream>

enum class Renk { Kirmizi, Yesil, Mavi };

int main() {
    Renk r = Renk::Yesil;

    // enum -> int
    int deger = static_cast<int>(r);
    std::cout << "Yesil = " << deger << std::endl;  // 1

    // int -> enum
    Renk r2 = static_cast<Renk>(2);
    if (r2 == Renk::Mavi) {
        std::cout << "Mavi!" << std::endl;
    }

    return 0;
}

static_cast ve Kalıtım (Inheritance)

Sınıf hiyerarşisinde üst sınıfa dönüşüm (upcasting) güvenlidir, alt sınıfa dönüşüm (downcasting) ise riskli olabilir:

#include <iostream>

class Hayvan {
public:
    virtual void ses() { std::cout << "..." << std::endl; }
    virtual ~Hayvan() = default;
};

class Kedi : public Hayvan {
public:
    void ses() override { std::cout << "Miyav!" << std::endl; }
    void tirmalama() { std::cout << "Tirmaladi!" << std::endl; }
};

int main() {
    Kedi kedi;

    // Upcast: Kedi* -> Hayvan* (guvenli, otomatik de olur)
    Hayvan* h = static_cast<Hayvan*>(&kedi);
    h->ses();  // "Miyav!" — polymorphism

    // Downcast: Hayvan* -> Kedi* (riskli! Tur uyusmazsa UB)
    Kedi* k = static_cast<Kedi*>(h);
    k->tirmalama();  // Calisir cunku gercekten Kedi

    return 0;
}

static_cast downcasting'de çalışma zamanı kontrolü yapmaz. Yanlış tipe dönüştürürsen tanımsız davranış (undefined behavior) oluşur. Güvenli downcasting için dynamic_cast kullan.


dynamic_cast — Güvenli Polimorfik Dönüşüm

dynamic_cast çalışma zamanında tür kontrolü yapar. Sadece polimorfik sınıflarda (en az bir virtual fonksiyon olan) çalışır:

#include <iostream>

class Hayvan {
public:
    virtual void ses() { std::cout << "..." << std::endl; }
    virtual ~Hayvan() = default;
};

class Kedi : public Hayvan {
public:
    void ses() override { std::cout << "Miyav!" << std::endl; }
    void tirmalama() { std::cout << "Tirmaladi!" << std::endl; }
};

class Kopek : public Hayvan {
public:
    void ses() override { std::cout << "Hav!" << std::endl; }
};

int main() {
    Hayvan* h1 = new Kedi();
    Hayvan* h2 = new Kopek();

    // Guvenli downcast — basarili
    Kedi* k1 = dynamic_cast<Kedi*>(h1);
    if (k1 != nullptr) {
        k1->tirmalama();  // Calisir
    }

    // Guvenli downcast — basarisiz (Kopek, Kedi degildir)
    Kedi* k2 = dynamic_cast<Kedi*>(h2);
    if (k2 != nullptr) {
        k2->tirmalama();  // Buraya girmez
    } else {
        std::cout << "Bu bir Kedi degil!" << std::endl;
    }

    delete h1;
    delete h2;
    return 0;
}

dynamic_cast Kuralları

  • Pointer ile: Başarısız olursa nullptr döner

  • Referans ile: Başarısız olursa std::bad_cast exception fırlatır

  • Virtual fonksiyon zorunlu: Polimorfik olmayan sınıflarda çalışmaz

  • Performans maliyeti var: RTTI (Run-Time Type Information) kullanır

#include <iostream>
#include <stdexcept>

class Base {
public:
    virtual ~Base() = default;
};

class Derived : public Base {
public:
    void ozel() { std::cout << "Ozel fonksiyon" << std::endl; }
};

int main() {
    Base b;
    // Referans ile dynamic_cast — hata firlatir
    try {
        Derived& d = dynamic_cast<Derived&>(b);
        d.ozel();
    } catch (const std::bad_cast& e) {
        std::cout << "Cast basarisiz: " << e.what() << std::endl;
    }

    return 0;
}

💡 İpucu: dynamic_cast çalışma zamanında kontrol yaptığı için static_cast'tan daha yavaştır. Ama güvenlik açısından çok daha iyidir. Downcasting gerekiyorsa ve tipten emin değilsen her zaman dynamic_cast kullan.


const_cast — const'u Kaldırma

const_cast bir değişkenden const niteleyicisini kaldırır veya ekler. Çok nadir kullanılır ve dikkatli olmak gerekir:

#include <iostream>

void eski_fonksiyon(char* str) {
    // Eski C fonksiyonu — const almaz ama degistirmez
    std::cout << str << std::endl;
}

int main() {
    const char* mesaj = "Merhaba";

    // const'u kaldir — eski API ile uyum icin
    eski_fonksiyon(const_cast<char*>(mesaj));

    // TEHLIKELI: gercekten const olan bir degeri degistirmek UB!
    const int sabit = 42;
    int* p = const_cast<int*>(&sabit);
    // *p = 100;  // TANIMSIZ DAVRANIS! Yapma!

    return 0;
}

Ne Zaman Kullanılır?

  • Eski C API'leri: const almayan ama veriyi değiştirmeyen eski fonksiyonlarla çalışırken

  • Aşırı yükleme (overloading): const ve non-const versiyonlar arasında kod tekrarını önlemek için

Genel kural: const_cast kullanman gerekiyorsa, muhtemelen tasarımda bir sorun var. Gerçekten const olan bir değeri değiştirmek tanımsız davranıştır.


reinterpret_cast — Ham Bellek Yeniden Yorumlama

reinterpret_cast bir pointer'ın tipini tamamen farklı bir pointer tipine çevirir. Bellekteki bitlere "başka bir gözle bakmak" gibidir:

#include <iostream>

int main() {
    int sayi = 42;
    int* int_ptr = &sayi;

    // int* -> char* — byte byte okuma
    char* byte_ptr = reinterpret_cast<char*>(int_ptr);

    std::cout << "Byte byte okuma:" << std::endl;
    for (size_t i = 0; i < sizeof(int); i++) {
        std::cout << "Byte " << i << ": "
                  << static_cast<int>(byte_ptr[i]) << std::endl;
    }

    // Pointer -> tamsayi donusumu (platform bagimlı)
    uintptr_t adres = reinterpret_cast<uintptr_t>(int_ptr);
    std::cout << "Adres (sayi): " << adres << std::endl;

    return 0;
}

Ne Zaman Kullanılır?

  • Ağ programlama (ham paket verileri işleme)

  • Donanım register'larına erişim

  • Serialization / deserialization

  • Düşük seviyeli sistem programlama

reinterpret_cast son çare olmalıdır. Çoğu program hiç kullanmadan yazılabilir. Kullanman gerekiyorsa, çok dikkatli ol ve neden gerektiğini yorum olarak belirt.


Cast Seçim Rehberi

Hangi cast'ı ne zaman kullanacağını şu karar ağacı ile belirleyebilirsin:

Tür dönüşümü mü gerekiyor?
│
├─ Sayısal dönüşüm (int↔double, int↔char)?
│  └─ static_cast
│
├─ Sınıf hiyerarşisinde downcast?
│  ├─ Tipten emin misin? → static_cast
│  └─ Emin değilsen? → dynamic_cast
│
├─ const kaldırma/ekleme?
│  └─ const_cast
│
├─ Ham bellek yeniden yorumlama?
│  └─ reinterpret_cast
│
└─ C-style cast? → KULLANMA!

Karşılaştırma Tablosu

CastDerleme ZamanıÇalışma ZamanıGüvenlikKullanım Sıklığı
static_castOrtaÇok yaygın
dynamic_castYüksekOrta
const_castDüşükNadir
reinterpret_castÇok düşükÇok nadir
C-style (int)xÇok düşükKULLANMA

Kullanıcı Tanımlı Dönüşümler (Kısa Giriş)

Kendi sınıflarında dönüşüm operatörleri tanımlayabilirsin:

#include <iostream>
#include <string>

class Sicaklik {
    double celsius;
public:
    explicit Sicaklik(double c) : celsius(c) {}

    // double'a otomatik donusum
    operator double() const { return celsius; }

    // string'e donusum
    explicit operator std::string() const {
        return std::to_string(celsius) + "°C";
    }
};

int main() {
    Sicaklik t{36.6};

    double deger = t;  // operator double() cagirilir
    std::cout << "Deger: " << deger << std::endl;

    // explicit oldugundan acik cast gerekir
    std::string metin = static_cast<std::string>(t);
    std::cout << "Metin: " << metin << std::endl;

    return 0;
}

explicit anahtar kelimesi istem dışı dönüşümleri engeller. Genel kural: dönüşüm operatörlerini `explicit` yap — istenmeyen otomatik dönüşümler zor bulunan hatalara yol açar.


Özet

  • Implicit dönüşüm küçük tipten büyüğe güvenlidir, büyükten küçüğe veri kaybına yol açar

  • C-style cast (int)x tehlikelidir, yeni kodda kullanma — hangi dönüşüm yapıldığı belli değildir

  • static_cast en yaygın cast'tır: sayısal dönüşümler, enum ve güvenli upcast için kullanılır

  • dynamic_cast çalışma zamanında kontrol eder, güvenli downcasting için vazgeçilmezdir (polimorfik sınıflarda)

  • const_cast ve reinterpret_cast çok nadir durumlar içindir — kullanımları riskli, dikkatle yaklaş

  • Süslü parantez {} ile başlatma narrowing conversion'ı engeller — her zaman tercih et