Tür Dönüşümleri
Özet
Önce tür dönüşümleri konusunda bilinmesi gerektiğini düşündüğüm noktaları vermek istiyorum. Yazının geri kalanını daha çok başvuru kaynağı olarak düşünebilirsiniz:
- Otomatik aritmetik tür dönüşümleri güvenli yönde yapılır: küçük türden büyük türe doğru ve değişebilen türden değişmez türe doğru
enumtürler tamsayı türlere otomatik olarak dönüşürler, ama tamsayılarenumtürlere otomatik olarak dönüşmezlerbooltüründefalse0'a,trueda 1'e otomatik olarak dönüşür; tersi de doğrudur: 0false'a, 1 detrue'ya dönüşür- Açıkça yapılan tür dönüşümünün söz dizimi şöyledir:
cast(Türİsmi)değer - Tür dönüşümlerinden kaçınmak daha iyidir
İşlemlerde kullanılan değişken ve nesne türlerinin hem o işlemlerle hem de birbirleriyle uyumlu olmaları gerekir. Yoksa anlamsız veya yanlış sonuçlar doğabilir. D'nin de aralarında bulunduğu bazı diller türlerin uyumluluklarını derleme zamanında denetlerler. Böyle dillere "türleri derleme zamanında belli olan" anlamında "statically typed" dil denir.
Anlamsız işlem örneği olarak, bir toplama işleminde sanki bir sayıymış gibi dizgi kullanmaya çalışan şu koda bakalım:
char[] dizgi;
dout.writefln(dizgi + 5);
Derleyici o kodu tür uyuşmazlığı nedeniyle reddeder. Bu yazıyı yazdığım sırada kullandığım derleyici, türlerin uyumsuz olduğunu bildiren şu hatayı veriyor:
Error: incompatible types for ((dizgi) + (5)): 'char[]' and 'int'
O hata mesajı, ((dizgi) + (5)) ifadesinde uyumsuz türler olduğunu belirtir: char[] ve int.
Tür uyumsuzluğu, farklı tür demek değildir. Çünkü farklı türlerin güvenle kullanılabildiği işlemler de vardır. Örneğin double türündeki bir değişkene int türünde bir değer eklenmesinde bir sakınca yoktur:
double toplam = 1.25; int artış = 3; toplam += artış;
toplam ve artış farklı türlerden oldukları halde o işlemde bir yanlışlık yoktur; çünkü bir kesirli sayının bir tamsayı kadar arttırılmasında bir uyumsuzluk olduğu düşünülemez.
Otomatik tür dönüşümleri
Her ne kadar bir double değerin bir int değer kadar arttırılmasında bir sakınca olmasa da, o işlemin mikro işlemcide yine de belirli bir türde yapılması gerekir. Kesirli sayılar dersinden hatırlayacağınız gibi; 64 bitlik olan double, 32 bitlik olan int'ten daha büyük (veya geniş) bir türdür. Bir int'e sığabilen her değer bir double'a da sığabilir.
Birbirinden farklı türler kullanılan işlemlerle karşılaştığında, derleyici önce değerlerden birisini diğer türe dönüştürür, ve işlemi ondan sonra gerçekleştirir. Bu dönüşümde kullanılan tür, değer kaybına neden olmayacak şekilde seçilir. Örneğin double türü int türünün bütün değerlerini tutabilir, ama bunun tersi doğru değildir. O yüzden yukarıdaki toplam += artış işlemi double türünde güvenle gerçekleştirilebilir.
Dönüştürülen değer, her zaman için isimsiz ve geçici bir değişken veya nesnedir. Asıl değerin kendisi değişmez. Örneğin yukarıdaki += işlemi sırasında artış'ın kendi türü değiştirilmez, ama artış'ın değerine eşit olan geçici bir değer kullanılır. Yukarıdaki işlemde perde arkasında neler olduğunu şöyle gösterebiliriz:
double aslında_isimsiz_olan_double_bir_deger = artış;
toplam += aslında_isimsiz_olan_double_bir_deger;
Derleyici, int değeri önce double türündeki geçici bir ara değere dönüştürür ve işlemde o dönüştürdüğü değeri kullanır. Bu örnekteki geçici değer yalnızca += işlemi süresince yaşar.
Böyle otomatik dönüşümler aritmetik işlemlerle sınırlı değildir. Birbirinin aynısı olmayan türlerin kullanıldığı başka durumlarda da otomatik tür dönüşümleri uygulanır. Eğer kullanılan türler bir dönüşüm sonucunda birlikte kullanılabiliyorlarsa, derleyici gerektikçe değerleri otomatik olarak dönüştürür. Örneğin int türünde parametre alan bir işleve byte türünde bir değer gönderilebilir:
void birİşlem(int sayı) { // ... } void main() { byte küçükDeğer = 7; birİşlem(küçükDeğer); // otomatik tür dönüşümü }
Orada da önce küçükDeğer'e eşit geçici bir int oluşturulur, ve birİşlem o geçici int değeri ile çağrılır.
Aritmetik dönüşümler
Aritmetik işlemlerde kullanılan değerler güvenli yönde, yani küçük türden büyük türe doğru gerçekleştirilirler. Bu kadarını akılda tutmak çoğu durumda yeterlidir.
Dönüşüm kuralları şöyledir:
- Değerlerden birisi
realise diğerireal'e dönüştürülür - Değilse ama birisi
doubleise diğeridouble'a dönüştürülür - Değilse ama birisi
floatise diğerifloat'a dönüştürülür - Değilse tamsayı terfisi uygulanır ve sonra şu işlemlere geçilir:
- Eğer iki tür de aynı ise durulur
- Eğer her ikisi de işaretli ise, veya her ikisi de işaretsiz ise; küçük tür büyük türe dönüştürülür
- Eğer işaretli tür işaretsiz türden büyükse, işaretsiz olan işaretliye dönüştürülür
- Hiçbirisi değilse işaretli tür işaretsiz türe dönüştürülür
Daha küçük tamsayı türünden daha büyük türe doğru yapılan tamsayı terfileri aşağıdaki tablodakine benzer:
| Hangi Türden | Hangi Türe |
|---|---|
| bool | int |
| byte | int |
| ubyte | int |
| short | int |
| ushort | int |
| char | int |
| wchar | int |
| dchar | uint |
Benzer dönüşümler diğer tamsayılar arasında da vardır. Örneğin short bir değer otomatik olarak long'a dönüşür.
Açıkça yapılan tür dönüşümleri
Bazı durumlarda bazı tür dönüşümlerini elle kendimiz yapmak zorunda kalabiliriz. Programcının isteğiyle yapılan tür dönüşümlerinin söz dizimi şöyledir:
cast(Türİsmi)değer
cast parantezinin içine hedef tür yazılır ve değer o türe dönüştürülür.
Yukarıda anlatıldığı gibi; int türünün kendisinden daha küçük olan short'a otomatik olarak dönüştürülmesi güvenli değildir. Bu yüzden derleyici aşağıdaki kodu kabul etmez:
void küçükTürleİşlem(short sayı) { // ... } void main() { int intSayı = 42; küçükTürleİşlem(intSayı); // ← derleme HATASI }
Eğer o işlev çağrısının yine de her zaman için güvenli olduğundan, örneğin intSayı'nın değerinin programın çalışması sırasında her zaman için short'a da sığabildiğinden eminsek, tür dönüşümünü elle kendimiz gerçekleştirebiliriz:
küçükTürleİşlem(cast(short)intSayı); // şimdi derlenir
O kodda intSayı'nın değerine eşit olan short türünde bir değer oluşturulur ve küçükTürleİşlem o geçici değerle çağrılır.
Burada intSayı'nın türünün değişmediğine özellikle dikkat çekmek istiyorum. Onun türünde bir değişiklik olmaz, onun değeri kullanılarak isimsiz bir short değer oluşturulur ve işlev o geçici değerle çağrılır.
const dönüşümü
Her referans türü, kendisinin const olanına otomatik olarak dönüşür. Bu güvenli bir dönüşümdür, çünkü hem zaten türün büyüklüğünde bir değişiklik olmaz, hem de const değerler değiştirilemezler:
dchar[] parantezİçineAl(const dchar[] metin) { return "{" ~ metin ~ "}"; } // ... dchar[] birSöz; birSöz ~= "merhaba dünya"; parantezİçineAl(birSöz);
O kodda sabit olmayan birSöz, sabit parametre alan işleve güvenle gönderilebilir, çünkü değerler sabit referanslar aracılığıyla değiştirilemezler.
Bunun tersi doğru değildir: const bir referans türü, const olmayan bir türe dönüşmez:
const double[string] kesirler = [ "çeyrek" : 0.25, "yarım" : 0.50 ]; double[string] başkaKesirler = kesirler; // ← derleme HATASI
kesirler const olduğu için, elemanların değiştirilmelerine izin vermez. O elemanların değiştirilmelerinin engellenebilmesi için onlara const olmayan başkaKesirler referansıyla erişilmesi engellenmelidir. Derleyici bu yüzden yukarıdaki dönüşümü reddeder.
Bu konu yalnızca referans değişkenleri ve referans türleri ile ilgilidir. Çünkü değer türlerinde zaten değer kopyalandığı için, kopyanın const olan asıl nesneyi değiştirmesi söz konusu olamaz:
const int köşeAdedi = 4; int kopyası = köşeAdedi; // derlenir (değer türü)
Yukarıdaki durumda const türden const olmayan türe dönüşüm yasaldır; çünkü dönüştürülen değer asıl değerin bir kopyası haline gelir; asıl değeri değiştirmesi söz konusu değildir.
Bu yüzden, otomatik const dönüşümleri yalnızca referanslarla ilgilidir.
enum dönüşümleri
enum dersinden hatırlayacağınız gibi, enum türleri isimli değerler kullanma olanağı sunarlar:
enum OyunKağıdıRengi { Maça, Kupa, Karo, Sinek }
Değerleri özellikle belirtilmediği için o tanımda değerler sıfırdan başlayarak ve birer birer arttırılarak atanır. Buna göre örneğin OyunKağıdıRengi.Sinek'in değeri 3 olur.
Böyle isimli enum değerleri, otomatik olarak tamsayı türlere dönüşürler. Örneğin aşağıdaki koddaki toplama işlemi sırasında OyunKağıdıRengi.Kupa 1 değerini alır ve sonuç 11 olur:
int sonuç = 10 + OyunKağıdıRengi.Kupa; assert(sonuç == 11);
Bunun tersi doğru değildir: tamsayı değerler enum türlerine otomatik olarak dönüşmezler. Örneğin aşağıdaki kodda renk değişkeninin 2 değerinin karşılığı olan OyunKağıdıRengi.Karo değerini almasını bekleyebiliriz; ama derlenemez:
OyunKağıdıRengi renk = 2; // ← derleme HATASI
D, tamsayıdan enum'a dönüşümün açıkça yapılmasını şart koşar:
OyunKağıdıRengi renk = cast(OyunKağıdıRengi)2; // derlenir
veya daha kısa olarak:
auto renk = cast(OyunKağıdıRengi)2;
bool dönüşümleri
bool türünün değerlerinden false 0'a, true da 1'e otomatik olarak dönüşür:
int birKoşul = false; assert(birKoşul == 0); int başkaKoşul = true; assert(başkaKoşul == 1);
Bunun tersi de doğrudur: 0 değeri false'a, 1 değeri de true'ya otomatik olarak dönüşür:
bool birDurum = 0; assert(!birDurum); // false bool başkaDurum = 1; assert(başkaDurum); // true
Sıfır ve bir dışındaki değerler otomatik olarak dönüşmezler.
Bir ifadeyi açıkça bool türüne dönüştürmek, o ifade 0 ise false, 0'dan farklı ise true değerini üretir:
bool birDurum = cast(bool)(5 - 5); assert(!birDurum); // false bool başkaDurum = cast(bool)(8 * 7); assert(başkaDurum); // true
Tür dönüşümlerinden kaçının
Hem otomatik hem de elle açıkça yapılan tür dönüşümleri programlarda şaşırtıcı durumlara neden olabilirler. Örnek olarak yukarıda gördüğümüz cast(short) dönüşümüne bakalım.
Kodda ileride yapılabilecek bir değişiklik artık intSayı'nın değerini short'a sığmayacak kadar büyük hale getirebilir ve cast(short)intSayı tür dönüşümü yanlış sonuçlara neden olabilir:
import std.cstream; void küçükTürleİşlem(short sayı) { dout.writefln("işlevdeki değer: ", sayı); } void main() { int intSayı = 40000; dout.writefln("asıl değer : ", intSayı); küçükTürleİşlem(cast(short)intSayı); }
Tamsayılar ve Aritmetik İşlemler dersinde gördüğümüz gibi, 40000 değeri short.max'tan daha büyük olduğu için taşar ve eksi bir değere dönüşür:
asıl değer : 40000
işlevdeki değer: -25536 ← yanlış değer
Bu yüzden tür dönüşümlerinden genel olarak kaçınmak gerekir. En iyisi, hangi işlemlerin hangi türlerle yapılacağına program tasarlanırken baştan karar vermek ve işlemleri olabildiğince tür dönüşümlerine başvurmadan o türlerle gerçekleştirmektir.
Ne yazık ki ne kadar iyi olurlarsa olsunlar tasarımlar eninde sonunda ya kendileri değişmek, ya da etkileştikleri başka kütüphanelere uymak zorunda kalırlar. Bu yüzden tür dönüşümleri az da olsa hemen hemen her programda bulunur.
D.ershane
Forum
Wiki
Projeler
Tanıtım
İletişim
Hakları