D Programlama Dili – Programlama dersleri ve D referansı
Ali Çehreli

çökme: [crash], programın hata ile sonlanması
değişken: [variable], kavramları temsil eden veya sınıf nesnesine erişim sağlayan program yapısı
eşleme tablosu: [associative array], elemanlarına tamsayı olmayan indekslerle de erişilebilen veri yapısı (bir 'hash table' gerçekleştirmesi)
ifade: [expression], programın değer oluşturan veya yan etki üreten bir bölümü
indeks: [index], topluluk elemanlarına erişmek için kullanılan bilgi
işleç: [operator], bir veya daha fazla ifadeyle iş yapan özel işaret (+, -, =, [], vs.)
nesne: [object], belirli bir sınıf veya yapı türünden olan değişken
sıradüzen: [hierarchy], sınıfların türeyerek oluşturdukları aile ağacı
üst sınıf: [super class], kendisinden sınıf türetilen sınıf
üye işlev: [member function], yapı veya sınıfın kendi tanımladığı işlemleri
... bütün sözlük



İngilizce Kaynaklar


Diğer




Object

Açıkça başka bir sınıftan türetilmeyen sınıflar otomatik olarak Object adlı sınıftan türerler.

Sıradüzenin en üstündeki sınıf Object'ten otomatik olarak türer:

class Çalgı : Object  {       // ": Object" yazılmaz; otomatiktir
    // ...
}

class TelliÇalgı : Çalgı {    // dolaylı olarak Object'ten türer
    // ...
}

En üstteki sınıf Object'ten türediği için, altındaki bütün sınıflar da dolaylı olarak Object'ten türerler. Bu anlamda "her sınıf, Object türündendir".

Bu türeme sonucunda her sınıf Object'in bazı üye işlevlerini edinir:

Bu işlevlerden son üçü sınıf nesnelerinin değerlerini ön plana çıkartmak ve onları eşleme tablolarında indeks türü olarak kullanmak için gereklidir.

Türeme yoluyla edinildikleri için bu işlevlerin türeyen tür için override anahtar sözcüğü ile tanımlanmaları gerekir.

Not: Object'ten edinilen başka üyeler de vardır. Bu bölümde yalnızca bu dört işlevini göreceğiz.

typeid ve TypeInfo

Object sınıfı, std pakedinin parçası olmayan object modülünde tanımlanmıştır. Bu modül türler hakkında bilgi taşıyan TypeInfo sınıfını da tanımlar. Programın çalışması sırasında her farklı tür için farklı bir TypeInfo nesnesi vardır. typeid ifadesi belirli bir türe karşılık gelen TypeInfo nesnesine erişim sağlar. Biraz aşağıda göreceğimiz gibi, TypeInfo sınıfı türlerin eşit olup olmadıklarını belirlemek yanında türlerin özel işlevlerine (çoğu bu kitapta gösterilmeyecek olan toHash, postblit, vs.) de erişim sağlar.

TypeInfo her zaman için çalışma zamanındaki asıl tür ile ilgilidir. Örneğin, aşağıdaki Kemençe ve Saz doğrudan TelliÇalgı sınıfından ve dolaylı olarak Çalgı sınıfından türemiş olsalar da ikisinin TypeInfo nesneleri farklıdır:

class Çalgı {
}

class TelliÇalgı : Çalgı {
}

class Kemençe : TelliÇalgı {
}

class Saz : TelliÇalgı {
}

void main() {
    TypeInfo k = typeid(Kemençe);
    TypeInfo s = typeid(Saz);
    assert(k != s);    // ← türler aynı değil
}

Yukarıdaki kullanımlarda typeid'ye türün kendisi verilmektedir (Kemençe gibi). typeid diğer kullanımında ifade alır ve o ifadenin değerinin türü ile ilgili olan TypeInfo nesnesini döndürür. Örneğin, aşağıdaki işlev birbirleriyle ilgili olsalar da farklı türden olan iki parametre almaktadır:

import std.stdio;

// ...

void foo(Çalgı ç, TelliÇalgı t) {
    const aynı_mı = (typeid(ç) == typeid(t));

    writefln("Türleri %s.", aynı_mı ? "aynı" : "aynı değil");
}

// ...

    auto a = new Kemençe();
    auto b = new Kemençe();
    foo(a, b);

Yukarıdaki foo çağrısı için gönderilen asıl parametre değerlerinin ikisi de Kemençe türünden olduklarından foo onların aynı türden olduklarını belirler:

Türleri aynı.

Kendilerine verilen ifadeleri işletmeyen .sizeof ve typeof'un aksine, typeid verilen ifadeyi işletmek zorundadır:

import std.stdio;

int foo(string bilgi) {
    writefln("'%s' sırasında çağrıldı.", bilgi);
    return 0;
}

void main() {
    const s = foo("sizeof").sizeof;     // foo() çağrılmaz
    alias T = typeof(foo("typeof"));    // foo() çağrılmaz
    auto ti = typeid(foo("typeid"));    // foo() çağrılır
}

Programın çıktısında görüldüğü gibi, yalnızca typeid'nin ifadesi işletilmiştir:

'typeid' sırasında çağrıldı.

Bu farkın nedeni, bazı ifadelerin asıl türlerinin ancak o ifadeler işletildikten sonra bilinebilmesidir. Örneğin, aşağıdaki işlevin asıl dönüş türü aldığı parametre değerine bağlı olarak her çağrıda ya Kemençe ya da Saz olacaktır:

Çalgı foo(int i) {
    return (i % 2) ? new Kemençe() : new Saz();
}
toString

Yapılarda olduğu gibi, toString sınıf nesnelerinin dizgi olarak kullanılmalarını sağlar:

    const saat = new Saat(20, 30, 0);
    writeln(saat);         // saat.toString()'i çağırır

Sınıfın Object'ten kalıtım yoluyla edindiği toString işlevi fazla kullanışlı değildir; döndürdüğü string yalnızca türün ismini içerir:

deneme.Saat

Sınıfın isminden önceki bölüm, yani yukarıdaki deneme, o sınıfı içeren modülün ismini belirtir. Ona bakarak, Saat sınıfının deneme.d isimli bir kaynak dosya içinde tanımlandığını anlayabiliriz.

Önceki bölümde olduğu gibi, anlamlı bir string üretmesi için bu işlev hemen hemen her zaman için özel olarak tanımlanır:

import std.string;

class Saat {
    override string toString() const {
        return format("%02s:%02s:%02s", saat, dakika, saniye);
    }

    // ...
}

class ÇalarSaat : Saat {
    override string toString() const {
        return format("%s ♫%02s:%02s", super.toString(),
                      alarmSaati, alarmDakikası);
    }

    // ...
}

// ...

    auto başucuSaati = new ÇalarSaat(20, 30, 0, 7, 0);
    writeln(başucuSaati);

Çıktısı:

20:30:00 ♫07:00
opEquals

İşleç Yükleme bölümünde gördüğümüz gibi, bu üye işlev == işlecinin tanımını belirler (ve dolaylı olarak != işlecinin tanımını). İşlevin dönüş değeri nesneler eşitlerse true, değillerse false olmalıdır.

Uyarı: Bu işlevin opCmp ile tutarlı olması gerekir; true döndürdüğü durumda opCmp da sıfır döndürmelidir.

Yapıların aksine, derleyici a == b gibi bir ifadeyi otomatik olarak a.opEquals(b) ifadesine dönüştürmez. İki sınıf nesnesi karşılaştırıldıklarında aşağıdaki dört adımlık algoritma uygulanır:

bool opEquals(Object a, Object b) {
    if (a is b) return true;                          // (1)
    if (a is null || b is null) return false;         // (2)
    if (typeid(a) == typeid(b)) return a.opEquals(b); // (3)
    return a.opEquals(b) && b.opEquals(a);            // (4)
}
  1. İki değişken de aynı nesneye erişim sağlıyorlarsa (veya ikisi de null iseler) eşittirler.
  2. Yalnızca birisi null ise eşit değildirler.
  3. Her iki nesne de aynı türden iseler ve o türün opEquals işlevi tanımlanmışsa a.opEquals(b) işletilir.
  4. Aksi taktirde, eşit olarak kabul edilebilmeleri için eğer tanımlanmışlarsa hem a.opEquals(b)'nin hem de b.opEquals(a)'nın true üretmeleri gerekir.

Dolayısıyla, opEquals programcı tarafından özellikle tanımlanmamışsa o sınıfın nesnelerinin değerlerine bakılmaz; iki sınıf değişkeninin aynı nesneye erişim sağlayıp sağlamadıklarına bakılır:

    auto değişken0 = new Saat(6, 7, 8);
    auto değişken1 = new Saat(6, 7, 8);

    assert(değişken0 != değişken1); // eşit değiller
                                    // (çünkü farklı nesneler)

Yukarıdaki koddaki iki nesne aynı parametre değerleriyle kuruldukları halde, new ile ayrı ayrı kurulmuş oldukları için iki farklı nesnedir. Bu yüzden, onlara erişim sağlayan değişken0 ve değişken1 değişkenleri Object'in gözünde eşit değillerdir.

Öte yandan, aynı nesneye erişim sağladıkları için şu iki değişken eşittir:

    auto ortak0 = new Saat(9, 10, 11);
    auto ortak1 = ortak0;

    assert(ortak0 == ortak1);       // eşitler
                                    // (çünkü aynı nesne)

Bazen nesneleri böyle kimliklerine göre değil, değerlerine göre karşılaştırmak isteriz. Örneğin değişken0'ın ve değişken1'in erişim sağladıkları nesnelerin değerlerinin eşit olmalarına bakarak, == işlecinin true üretmesini bekleyebiliriz.

Yapılardan farklı olarak, ve Object'ten kalıtımla edinildiği için, opEquals işlevinin parametresi Object'tir. O yüzden, bu işlevi kendi sınıfımız için tanımlarken parametresini Object olarak yazmamız gerekir:

class Saat {
    override bool opEquals(Object o) const {
        // ...
    }

    // ...
}

Kendimiz Object olarak doğrudan kullanmayacağımız için bu parametrenin ismini kullanışsız olarak o diye seçmekte bir sakınca görmüyorum. İlk ve çoğu durumda da tek işimiz, onu bir tür dönüşümünde kullanmak olacak.

opEquals'a parametre olarak gelen nesne, kod içinde == işlecinin sağ tarafında yazılan nesnedir. Örneğin şu iki satır birbirinin eşdeğeridir:

    değişken0 == değişken1;    // o, değişken1'i temsil eder

Bu işleçteki amaç bu sınıftan iki nesneyi karşılaştırmak olduğu için, işlevi tanımlarken yapılması gereken ilk şey, parametre olarak gelen Object'in türünü kendi sınıfımızın türüne dönüştürmektir. Sağdaki nesneyi değiştirmek gibi bir niyetimiz de olmadığı için, tür dönüşümünde const belirtecini kullanmak da uygun olur:

    override bool opEquals(Object o) const {
        const sağdaki = cast(const Saat)o;

        // ...
    }

Hatırlayacağınız gibi, tür dönüşümü için std.conv.to işlevi de kullanılabilir:

import std.conv;
// ...
        const sağdaki = to!(const Saat)(o);

Yukarıdaki tür dönüşümü işlemi ya sağdaki'nin türünü bu şekilde const Saat olarak belirler ya da dönüşüm uyumsuzsa null üretir.

Burada karar verilmesi gereken önemli bir konu, sağdaki nesnenin türünün bu nesnenin türü ile aynı olmadığında ne olacağıdır. Sağdaki nesnenin tür dönüşümü sonucunda null üretmesi, sağdaki nesnenin aslında bu türe dönüştürülemediği anlamına gelir.

Ben nesnelerin eşit kabul edilebilmeleri için bu dönüşümün başarılı olması gerektiğini varsayacağım. Bu yüzden eşitlik karşılaştırmalarında öncelikle sağdaki'nin null olmadığına bakacağım. Zaten null olduğu durumda sağdaki'nin üyelerine erişmek hatalıdır:

class Saat {
    int saat;
    int dakika;
    int saniye;

    override bool opEquals(Object o) const {
        const sağdaki = cast(const Saat)o;

        return (sağdaki &&
                (saat == sağdaki.saat) &&
                (dakika == sağdaki.dakika) &&
                (saniye == sağdaki.saniye));
    }

    // ...
}

İşlevin bu tanımı sayesinde, == işleci Saat nesnelerini artık değerlerine göre karşılaştırır:

    auto değişken0 = new Saat(6, 7, 8);
    auto değişken1 = new Saat(6, 7, 8);

    assert(değişken0 == değişken1); // artık eşitler
                                    // (çünkü değerleri aynı)

opEquals'ı tanımlarken, eğer varsa ve nesnelerin eşit kabul edilmeleri için gerekliyse, üst sınıfın üyelerini de unutmamak gerekir. Örneğin alt sınıf olan ÇalarSaat'in nesnelerini karşılaştırırken, Saat'ten kalıtımla edindiği parçaları da karşılaştırmak anlamlı olur:

class ÇalarSaat : Saat {
    int alarmSaati;
    int alarmDakikası;

    override bool opEquals(Object o) const {
        const sağdaki = cast(const ÇalarSaat)o;

        return (sağdaki &&
                (alarmSaati == sağdaki.alarmSaati) &&
                (alarmDakikası == sağdaki.alarmDakikası) &&
                super.opEquals(o));
    }

    // ...
}

Oradaki ifade super'in opEquals işlevini çağırır ve eşitlik kararında onun da sonucunu kullanmış olur. Onun yerine daha kısaca super == o da yazılabilir. Ancak, öyle yazıldığında yukarıdaki dört adımlı algoritma tekrar işletileceğinden kod biraz daha yavaş olabilir.

opCmp

Sınıf nesnelerini sıralamak için kullanılır. <, <=, >, ve >= işleçlerinin tanımı için perde arkasında bu işlev kullanılır.

Bu işlevin dönüş değerini < işleci üzerinde düşünebilirsiniz: Soldaki nesne önce olduğunda eksi bir değer, sağdaki nesne önce olduğunda artı bir değer, ikisi eşit olduklarında sıfır döndürmelidir.

Uyarı: Bu işlevin opEquals ile tutarlı olması gerekir; sıfır döndürdüğü durumda opEquals da true döndürmelidir.

toString'in ve opEquals'un aksine, bu işlevin Object sınıfından kalıtımla edinilen bir davranışı yoktur. Tanımlanmadan kullanılırsa hata atılır:

    auto değişken0 = new Saat(6, 7, 8);
    auto değişken1 = new Saat(6, 7, 8);

    assert(değişken0 <= değişken1);     // ← Hata atılır
object.Exception: need opCmp for class deneme.Saat

Yukarıda opEquals için söylenenler bu işlev için de geçerlidir: Sağdaki nesnenin türünün bu nesnenin türüne eşit olmadığı durumda hangisinin daha önce sıralanması gerektiği konusuna bir şekilde karar vermek gerekir.

Bunun en kolayı bu kararı derleyiciye bırakmaktır, çünkü derleyici türler arasında zaten genel bir sıralama belirler. Türler aynı olmadıklarında bu sıralamadan yararlanmanın yolu, typeid'lerinin opCmp işlevinden yararlanmaktır:

class Saat {
    int saat;
    int dakika;
    int saniye;

    override int opCmp(Object o) const {
        /* Türler aynı olmadıklarında türlerin genel
         * sıralamasından yararlanıyoruz. */
        if (typeid(this) != typeid(o)) {
            return typeid(this).opCmp(typeid(o));
        }

        const sağdaki = cast(const Saat)o;
        /* sağdaki'nin null olup olmadığına bakmaya gerek yok
         * çünkü buraya gelinmişse 'o' ile aynı türdendir. */

        if (saat != sağdaki.saat) {
            return saat - sağdaki.saat;

        } else if (dakika != sağdaki.dakika) {
            return dakika - sağdaki.dakika;

        } else {
            return saniye - sağdaki.saniye;
        }
    }

    // ...
}

Yukarıdaki tanım, nesneleri sıralama amacıyla karşılaştırırken öncelikle türlerinin uyumlu olup olmadıklarına bakıyor. Eğer uyumlu iseler saat bilgisini dikkate alıyor; saatler eşitlerse dakikalara, onlar da eşitlerse saniyelere bakıyor.

Ne yazık ki, bu işlevin bu gibi karşılaştırmalarda daha güzel veya daha etkin bir yazımı yoktur. Eğer daha uygun bulursanız, if-else-if zinciri yerine onun eşdeğeri olan üçlü işleci de kullanabilirsiniz:

    override int opCmp(Object o) const {
        if (typeid(this) != typeid(o)) {
            return typeid(this).opCmp(typeid(o));
        }

        const sağdaki = cast(const Saat)o;

        return (saat != sağdaki.saat
                ? saat - sağdaki.saat
                : (dakika != sağdaki.dakika
                   ? dakika - sağdaki.dakika
                   : saniye - sağdaki.saniye));
    }

Bu işlevi bir alt sınıf için tanımlarken ve karşılaştırmada önemi varsa, üst sınıfını da unutmamak gerekir. Örneğin, aşağıdaki ÇalarSaat.opCmp sıralama kararında öncelikle üst sınıfından yararlanıyor:

class ÇalarSaat : Saat {
    override int opCmp(Object o) const {
        const sağdaki = cast(const ÇalarSaat)o;

        const int üstSonuç = super.opCmp(o);

        if (üstSonuç != 0) {
            return üstSonuç;

        } else if (alarmSaati != sağdaki.alarmSaati) {
            return alarmSaati - sağdaki.alarmSaati;

        } else {
            return alarmDakikası - sağdaki.alarmDakikası;
        }
    }

    // ...
}

Üst sınıfın sıfırdan farklı bir değer döndürmesi durumunda, iki nesnenin sıraları ile ilgili yeterli bilgi edinilmiştir; ve o değer döndürülür. Yukarıdaki kodda, alt sınıfın üyelerine ancak üst sınıf parçaları eşit çıktığında bakılmaktadır.

Artık bu türün nesneleri sıralama karşılaştırmalarında kullanılabilir:

    auto çs0 = new ÇalarSaat(8, 0, 0, 6, 30);
    auto çs1 = new ÇalarSaat(8, 0, 0, 6, 31);

    assert(çs0 < çs1);

O kodda diğer bütün üyeleri eşit olduğu için, çs0 ve çs1'in nasıl sıralanacaklarını en son bakılan alarm dakikası belirler.

Bu işlev yalnızca kendi yazdığımız kodlarda kullanılmak için değildir. Programda kullandığımız kütüphaneler ve dil olanakları da bu işlevi çağırabilir. Örneğin bir dizi içindeki nesnelerin sort ile sıralanmalarında, veya sınıfın bir eşleme tablosunda indeks türü olarak kullanılmasında da perde arkasında bu işlevden yararlanılır.

Dizgi türünden olan üyeler için opCmp

Dizgi türündeki üyeler için opCmp işlevini eksi, sıfır, veya artı döndürecek şekilde uzun uzun şöyle yazabilirsiniz:

import std.exception;

class Öğrenci {
    string isim;

    override int opCmp(Object o) const {
        const sağdaki = cast(Öğrenci)o;
        enforce(sağdaki);

        if (isim < sağdaki.isim) {
            return -1;

        } else if (isim > sağdaki.isim) {
            return 1;

        } else {
            return 0;
        }
    }

    // ...
}

Onun yerine, std.algorithm modülünde tanımlanmış olan ve aynı karşılaştırmayı daha hızlı olarak gerçekleştiren cmp işlevini de kullanabilirsiniz:

import std.algorithm;

class Öğrenci {
    string isim;

    override int opCmp(Object o) const {
        const sağdaki = cast(Öğrenci)o;
        enforce(sağdaki);

        return cmp(isim, sağdaki.isim);
    }

    // ...
}

Bu türün, kendisiyle uyumsuz olan türlerle sıra karşılaştırılmasında kullanılmasına izin vermediğine dikkat edin. Bu denetimi Object'ten Öğrenci'ye tür dönüşümünün başarılı olmasına enforce ile bakarak sağlıyor.

toHash

Bu işlev, sınıfın eşleme tablolarında indeks türü olarak kullanılabilmesini sağlar. Eşleme tablosunun eleman türü olarak kullanıldığı durumda bir etkisi yoktur.

Uyarı: Yalnızca bu işlevi tanımlamak yetmez. Bu işlevin eşleme tablolarında doğru olarak kullanılabilmesi için opEquals ve opCmp işlevlerinin de birbirleriyle tutarlı olarak tanımlanmış olmaları gerekir.

Eşleme tablosu indeks değerleri

Eşleme tabloları eleman erişimini çok hızlı şekilde gerçekleştiren veri yapılarıdır. Üstelik bunu, tabloda ne kadar eleman bulunduğundan bağımsız olarak yapabilirler. (Not: Her şeyin olduğu gibi bu hızın da bir bedeli vardır: elemanları sırasız olarak tutmak zorundadırlar, ve kesinlikle gereken miktardan daha fazla bellek kullanıyor olabilirler.)

Eşleme tablolarının bu hızı, indeks olarak kullanılan türü önce hash denen bir tamsayı değere çevirmelerinden kaynaklanır. Bu tamsayıyı kendilerine ait bir dizinin indeksi olarak kullanırlar.

Bu yöntemin hızdan başka bir yararı, tamsayıya dönüştürülebilen her türün eşleme tablosu indeks türü olarak kullanılabilmesidir.

toHash, sınıf nesnelerinin bu amaç için indeks değerleri döndürmelerini sağlar.

Bu sayede, pek mantıklı olmasa da, Saat türünü bile indeks olarak kullanabiliriz:

    string[Saat] zamanİsimleri;

    zamanİsimleri[new Saat(12, 0, 0)] = "öğleni gösteren saat";

Object'ten kalıtım yoluyla edinilen toHash işlevi, farklı nesneler için farklı indeks değerleri üretecek şekilde tanımlanmıştır. Bu, opEquals'un farklı nesnelerin eşit olmadıklarını kabul etmesine benzer.

Yukarıdaki kod Saat sınıfı için özel bir toHash işlevi tanımlanmamış olsa bile derlenir; ama istediğimiz gibi çalışmaz. Yukarıdaki tabloya eklenmiş olan Saat nesnesi ile aynı değere sahip olan, ama ondan farklı bir Saat nesnesi ile erişmek istesek; doğal olarak tablodaki "öğleni gösteren saat" değerini bulmayı bekleriz:

    if (new Saat(12, 0, 0) in zamanİsimleri) {
        writeln("var");

    } else {
        writeln("yok");
    }

Ne yazık ki, oradaki in işleci false döndürür; yani bu nesnenin tabloda bulunmadığını belirtir:

yok

Bunun nedeni, yerleştirilirken kullanılan nesne ile erişirken kullanılan nesnenin new ile ayrı ayrı oluşturulmuş olmalarıdır; yani ikisi farklı nesnelerdir.

Dolayısıyla; Object'ten kalıtımla edinilen toHash, eşleme tablolarında indeks değeri olarak kullanılmaya çoğu durumda elverişli değildir. toHash'i, bir tamsayı indeks döndürecek şekilde bizim yazmamız gerekir.

toHash için seçilecek üyeler

İndeks değeri, nesnenin üyeleri kullanılarak hesaplanır. Ancak, her üye bu indeks hesabına uygun değildir.

Bunun için seçilecek üyeler, nesneyi diğer nesnelerden ayırt etmeye yarayan üyeler olmalıdır. Örneğin Öğrenci gibi bir sınıfın isim ve soyad üyelerinin ikisi birden nesneleri ayırt etmek için kullanılabilir; çünkü bu iki üyenin her nesnede farklı olduğunu düşünebiliriz. (İsim benzerliklerini gözardı ediyorum.)

Öte yandan, Öğrenci sınıfının notlar dizisi uygun değildir; çünkü hem birden fazla nesnede aynı not değerleri bulunabilir; hem de aynı öğrencinin notları zamanla değişebilir.

İndeks değerinin hesaplanması

İndeks değerinin hesabı eşleme tablosunun hızını doğrudan etkiler. Üstelik, her hesap her çeşit veri üzerinde aynı derece etkili değildir. Uygun hesaplama yöntemleri bu kitabın kapsamı dışında kaldığı için bu konunun ayrıntısına girmeyeceğim ve genel bir ilke vermekle yetineceğim: Genel olarak, değerlerinin farklı oldukları kabul edilen nesnelerin farklı indeks değerlerinin olması etkinlik açısından iyidir. Farklı değerli nesnelerin aynı indeks değerini üretmeleri hata değildir; performans açısından istenmeyen bir durumdur.

Saat nesnelerinin farklı kabul edilebilmeleri için bütün üyelerinin değerlerinin önemli olduğunu düşünebiliriz. Bu yüzden, indeks değeri olarak o üç üyeden yararlanılarak elde edilen bir tamsayı değer kullanılabilir. Eğer indeks değeri olarak gece yarısından kaç saniye ötede olunduğu kullanılırsa, herhangi bir üyesi değişik olan iki nesnenin indeks değerlerinin farklı olacağı garanti edilmiş olur:

class Saat {
    int saat;
    int dakika;
    int saniye;

    override size_t toHash() const {
        // Saatte 3600 ve dakikada 60 saniye bulunduğu için:
        return (3600 * saat) + (60 * dakika) + saniye;
    }

    // ...
}

Eşleme tablolarında indeks türü olarak Saat kullanıldığında artık programcı tarafından tanımlanmış olan bu toHash kullanılır. Bunun sonucunda, yukarıdaki kodda new ile farklı olarak kurulmuş olan iki nesnenin saat, dakika, ve saniye değerleri aynı olduğundan eşleme tablosunda aynı indeks değeri üretilir.

Programın çıktısı artık beklenen sonucu verir:

var

Önceki işlevlerde olduğu gibi, üst sınıfı unutmamak gerekebilir. Örneğin, ÇalarSaat'in toHash işlevi Saat'inkinden şöyle yararlanabilir:

class ÇalarSaat : Saat {
    int alarmSaati;
    int alarmDakikası;

    override size_t toHash() const {
        return super.toHash() + alarmSaati + alarmDakikası;
    }

    // ...
}

Not: Yukarıdaki hesabı bir örnek olarak kabul edin. Tamsayı değerleri toplayarak üretilen indeks değerleri genelde eşleme tablosu performansı açısından iyi değildir.

D; kesirli sayılar, diziler, ve yapı türleri için çoğu duruma uygun olan indeks değeri algoritmaları kullanır. Bu algoritmalardan programcı da yararlanabilir.

Kulağa karmaşık geldiği halde aslında çok kısaca yapmamız gereken; önce typeid'yi üye ile, sonra da typeid'nin döndürdüğü nesnenin getHash üye işlevini üyenin adresi ile çağırmaktır. Hepsinin dönüş değeri, o üyeye uygun bir indeks değeridir. Bu; kesirli sayılar, diziler ve yapılar için hep aynı şekilde yazılır.

Öğrencinin ismini bir string üyesinde tutan ve eşleme tabloları için indeks değeri olarak bundan yararlanmak isteyen bir sınıfın toHash işlevi şöyle yazılabilir:

class Öğrenci {
    string isim;

    override size_t toHash() const {
        return typeid(isim).getHash(&isim);
    }

    // ...
}
Yapılar için toHash

Yapılar değer türleri olduklarından onların indeks değerleri zaten otomatik olarak ve etkin bir algoritmayla hesaplanır. O algoritma nesnenin bütün üyelerini dikkate alır.

Eğer herhangi bir nedenle, örneğin bir öğrenci yapısının not bilgisini dışarıda bırakacak şekilde kendiniz yazmak isterseniz; toHash'i yapılar için de tanımlayabilirsiniz.

Problemler
  1. Elimizde renkli noktaları ifade eden bir sınıf olsun:
    enum Renk { mavi, yeşil, kırmızı }
    
    class Nokta {
        int x;
        int y;
        Renk renk;
    
        this(int x, int y, Renk renk) {
            this.x = x;
            this.y = y;
            this.renk = renk;
        }
    }
    

    Bu sınıfın opEquals işlevini rengi gözardı edecek şekilde yazın. Şu iki nokta, renkleri farklı olduğu halde eşit çıksınlar. Yani assert denetimi doğru çıksın:

        // Renkleri farklı
        auto maviNokta = new Nokta(1, 2, Renk.mavi);
        auto yeşilNokta = new Nokta(1, 2, Renk.yeşil);
    
        // Yine de eşitler
        assert(maviNokta == yeşilNokta);
    
  2. Aynı sınıf için opCmp işlevini öncelikle x'e sonra y'ye bakacak şekilde yazın. Aşağıdaki assert denetimleri doğru çıksın:
        auto kırmızıNokta1 = new Nokta(-1, 10, Renk.kırmızı);
        auto kırmızıNokta2 = new Nokta(-2, 10, Renk.kırmızı);
        auto kırmızıNokta3 = new Nokta(-2,  7, Renk.kırmızı);
    
        assert(kırmızıNokta1 < maviNokta);
        assert(kırmızıNokta3 < kırmızıNokta2);
    
        /* Mavi renk daha önce olduğu halde, renk gözardı
         * edildiğinden maviNokta yeşilNokta'dan daha önce
         * olmamalıdır. */
        assert(!(maviNokta < yeşilNokta));
    

    Bu sınıfın opCmp işlevini de yukarıda Öğrenci sınıfında olduğu gibi uyumsuz türlerin karşılaştırılmalarını desteklemeyecek biçimde gerçekleştirebilirsiniz.

  3. Üç noktayı bir araya getiren başka bir sınıf olsun:
    class ÜçgenBölge {
        Nokta[3] noktalar;
    
        this(Nokta bir, Nokta iki, Nokta üç) {
            noktalar = [ bir, iki, üç ];
        }
    }
    

    O sınıf için toHash işlevini bütün noktalarını kullanacak biçimde yazın. Yine aşağıdaki assert'ler doğru çıksın:

        /* bölge1 ve bölge2, değerleri aynı olan farklı noktalarla
         * kuruluyorlar. (Hatırlatma: maviNokta ve yeşilNokta
         * değer olarak eşit kabul ediliyorlardı.) */
        auto bölge1 =
            new ÜçgenBölge(maviNokta, yeşilNokta, kırmızıNokta1);
        auto bölge2 =
            new ÜçgenBölge(yeşilNokta, maviNokta, kırmızıNokta1);
    
        // Yine de eşitler
        assert(bölge1 == bölge2);
    
        // Bir eşleme tablosu
        double[ÜçgenBölge] bölgeler;
    
        // bölge1 ile indeksleniyor
        bölgeler[bölge1] = 1.25;
    
        // bölge2 ile de aynı veriye erişiliyor
        assert(bölge2 in bölgeler);
        assert(bölgeler[bölge2] == 1.25);
    

    toHash tanımlandığında opEquals ve opCmp işlevlerinin de tanımlanmaları gerektiğini unutmayın.