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 doubleYani 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, amaint 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:
const_caststatic_caststatic_cast+const_castreinterpret_castreinterpret_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_castvb.) 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
nullptrdönerReferans ile: Başarısız olursa
std::bad_castexception fırlatırVirtual 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çinstatic_cast'tan daha yavaştır. Ama güvenlik açısından çok daha iyidir. Downcasting gerekiyorsa ve tipten emin değilsen her zamandynamic_castkullan.
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:
constalmayan ama veriyi değiştirmeyen eski fonksiyonlarla çalışırkenAşı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
| Cast | Derleme Zamanı | Çalışma Zamanı | Güvenlik | Kullanım Sıklığı |
|---|---|---|---|---|
static_cast | ✅ | ❌ | Orta | Çok yaygın |
dynamic_cast | ❌ | ✅ | Yüksek | Orta |
const_cast | ✅ | ❌ | Düşük | Nadir |
reinterpret_cast | ✅ | ❌ | Çok düşük | Çok nadir |
C-style (int)x | ✅ | ❌ | Çok düşük | KULLANMA |
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)xtehlikelidir, yeni kodda kullanma — hangi dönüşüm yapıldığı belli değildirstatic_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
AI Asistan
Sorularını yanıtlamaya hazır