D.ershane D Programlama Dili Dersleri

açıkça elle yapılan: [explicit], programcı tarafından açık olarak yapılan
değişmez: [immutable], programın çalışması süresince kesinlikle değişmeyen
derleyici: [compiler], programlama dili kodunu bilgisayarın anladığı makine koduna çeviren program
dinamik: [dynamic], çalışma zamanında değişebilen
geçici: [temporary], bir işlem için geçici olarak oluşturulan ve yaşamı kısa süren değişken veya nesne
işaretli tür: [signed type], eksi ve artı değer alabilen tür
işaretsiz tür: [unsigned type], yalnızca artı değer alabilen tür
mikro işlemci: [CPU], bilgisayarın beyni
otomatik: [implicit], derleyici tarafından otomatik olarak yapılan
referans türü: [reference type], başka bir nesneye erişim sağlayan tür
sabit: [const], bir bağlamda değiştirilmeyen
statik: [static], derleme zamanında belirli olan
tür dönüşümü: [type conversion], bir değeri kullanarak başka bir türden değer elde etmek
... bütün sözlük

Bölümler
İngilizce Kaynaklar
Diğer



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:


İş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:

  1. Değerlerden birisi real ise diğeri real'e dönüştürülür
  2. Değilse ama birisi double ise diğeri double'a dönüştürülür
  3. Değilse ama birisi float ise diğeri float'a dönüştürülür
  4. Değilse tamsayı terfisi uygulanır ve sonra şu işlemlere geçilir:
    1. Eğer iki tür de aynı ise durulur
    2. 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
    3. Eğer işaretli tür işaretsiz türden büyükse, işaretsiz olan işaretliye dönüştürülür
    4. 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.