D.ershane D Programlama Dili Dersleri

atama: [assign], değişkene yeni bir değer vermek
işleç: [operator], bir veya daha fazla değişkenle iş yapan özel işaret (||, &&, +, -, =, vs.)
kısıtlama: [constraint], şablon parametrelerinin uyması gereken koşulların belirlenmesi
kurma: [construct], yapı veya sınıf nesnesini kullanılabilir duruma getirmek
şablon: [template], derleyicinin örneğin 'türden bağımsız programlama' için kod üretme düzeneği
yükleme: [overloading], aynı isimde birden çok işlev tanımlama
... bütün sözlük

Bölümler
İngilizce Kaynaklar
Diğer



İşleç Yükleme

Bu derste yapılar üzerinde anlatılanlar daha sonra göreceğimiz sınıflar için de hemen hemen aynen geçerlidir. En belirgin fark, özel işlevler dersinde gördüğümüz atama işleci opAssign'ın sınıflar için tanımlanamıyor olmasıdır.

İşleç yükleme, işleçlerin kendi türlerimizle nasıl çalışacaklarını belirleme olanağıdır.

Yapıların ve onlar için özel olarak tanımlayabildiğimiz üye işlevlerin yararlarını önceki derslerde gördük. Bunun bir örneği, GününSaati nesnelerine Süre nesnelerini ekleyebilmekti. Kodu kısa tutmak için yalnızca bu dersi ilgilendiren üyelerini gösteriyorum:

struct Süre
{
    int dakika;
}

struct GününSaati
{
    int saat;
    int dakika;

    void ekle(in Süre süre)
    {
        dakika += süre.dakika;

        saat += dakika / 60;
        dakika %= 60;
        saat %= 24;
    }
}

void main()
{
    auto yemekZamanı = GününSaati(12, 0);
    yemekZamanı.ekle(Süre(10));
}

Üye işlevlerin yararı, yapı ile ilgili işlemlerin yapı tanımı ile aynı yerde yapılabilmesidir. Üyeler ve işlevler bir arada tanımlanınca, yapının üyelerinin bütün işlevler tarafından doğru olarak kullanıldıkları da hep birden denetlenebilir.

Bütün bu yararlarına karşın, temel türlerle karşılaştırıldıklarında yapıların bir yetersizlikleri ortaya çıkar: Temel türler, özel hiçbir şey yapmaya gerek olmadan işleçlerle rahatça kullanılabilirler:

    int ağırlık = 50;
    ağırlık += 10;                     // işleçle

Şimdiye kadar öğrendiğimiz bilgiler doğrultusunda benzer işlemleri yapılar için ancak üye işlevlerle gerçekleştirebiliyoruz:

    auto yemekZamanı = GününSaati(12, 0);
    yemekZamanı.ekle(Süre(10));        // üye işlevle

İşleç yükleme olanağı bu konuda yarar sağlar: Yapıları da temel türlerde olduğu gibi işleçlerle kullanabilme olanağı sunar. Örneğin GününSaati yapısı için tanımlayabileceğimiz += işleci, yukarıdaki işlemi temel türlerde olduğu gibi doğal olarak yazmamızı sağlar:

    yemekZamanı += Süre(10);           // yapı için de işleçle

Yüklenebilecek bütün işleçlere geçmeden önce bu kullanımın nasıl sağlandığını göstermek istiyorum. Daha önce ismini ekle olarak yazdığımız işlevi, D'nin özel olarak belirlediği opOpAssign(string işleç) ismiyle tanımlamak ve bu tanımın "+" işlemi için yapılmakta olduğunu belirtmek gerekir.

Şimdiye kadar gördüğümüz işlev tanımlarından farklı olan bu tanım, opOpAssign aslında bir işlev şablonu olduğundandır. Şablonları daha ilerideki derslerde göstereceğim; şimdilik işleç yükleme konusunda bu söz dizimini bir kalıp olarak uygulamak gerektiğini kabul etmenizi rica ediyorum:

struct GününSaati
{
// ...
    void opOpAssign(string işleç)(in Süre süre)   // (1)
        if (işleç == "+")                         // (2)
    {
        dakika += süre.dakika;

        saat += dakika / 60;
        dakika %= 60;
        saat %= 24;
    }
}

Yukarıdaki şablon tanımı iki parçadan oluşur:

  1. üyeİşlevİsmi(string işleç): Yukarıdaki opOpAssign'da olduğu gibi üyeİşlevİsmi'nin ve (string işleç)'in aynen yazılmaları gerekir; kullanılabilecek bütün üye işlev isimlerini aşağıda göreceğiz.
  2. if (işleç == "işleç_karakteri"): Aynı üye işlev ismi birden fazla işlev için kullanıldığından hangi işleç karakterinin tanımlanmakta olduğu bu söz dizimiyle belirtilir. Aslında bir şablon kısıtlaması olan bu söz diziminin ayrıntılarını da daha sonraki bir derste göreceğiz.

Derleyici, GününSaati nesnelerinin += işleciyle kullanıldığı yerlerde perde arkasında opOpAssign!"+" işlevini çağırır:

    yemekZamanı += Süre(10);
    // Üsttekinin eşdeğeri:
    yemekZamanı.opOpAssign!"+"(Süre(10));

opOpAssign'dan sonra yazılan !"+", opOpAssign'ın + işleci için tanımı olan işlevin çağrılmakta olduğu anlamına gelir. Şablonlarla ilgili olan bu söz dizimini de daha ilerideki bir derste göstereceğim.

Kod içindeki += işlecine karşılık gelen üye işlevde "+=" değil, "+" kullanıldığına dikkat edin. opOpAssign'ın isminde geçen ve "değer ata" anlamına gelen "assign", zaten atama kavramını içerir; yani opOpAssign!"+", atamalı toplama işlemi olan += işlecinin tanımıdır.

Alışılmış olan davranışları değiştirmeyin

İşleçlerin davranışlarını bizim belirleyebiliyor olmamız, bize çoğu işleç için istediğimiz şeyi yapma serbestisi verir. Örneğin yukarıdaki işlevin içeriğini süre ekleyecek şekilde değil, tam tersine süre azaltacak şekilde de yazabilirdik. Oysa kodu okuyanlar += işlecini gördüklerinde doğal olarak değerin artması gibi bir davranış bekleyeceklerdir.

İşleçleri doğal davranışları dışında yazdığınızda bunun herkesi yanıltacağını ve programda hatalara neden olacağını aklınızda bulundurun.

Tekli işleç

Tek nesneyle işleyen işleçlere tekli işleç denir:

    ++ağırlık;

++ işleci tekli işleçtir çünkü işini yaparken yalnızca ağırlık'ı kullanır ve onun değerini bir arttırır.

İkili işleç

İki nesne kullanan işleçlere ikili işleç denir:

    toplamAğırlık = kutuAğırlığı + çikolataAğırlığı;

Yukarıdaki ifadede iki farklı ikili işleç görüyoruz: + işleci, kendisinin solundaki ve sağındaki değerleri toplar; = işleci de sağındakinin değerini solundakine atar.

Bu derste yüklenebilen bütün işleçlerin örneklerini vermeyeceğim; yalnızca çok kullanıldığını düşündüğüm işleçleri anlatacağım. Zaten bu işleçlerin bazılarını yüklemeye belki de hiç ihtiyacınız olmayacak.

Yüklenebilen tekli işleçler

Bu işleçler opUnary üye işlev ismiyle tanımlanırlar; parametre almazlar çünkü yalnızca işlecin kullanıldığı nesneyi etkilerler.

İşlev tanımında kullanılması gereken işleç dizgileri şunlardır:

İşleç Anlamı İşleç Dizgisi
-nesne ters işaretlisini üret "-"
+nesne aynı işaretlisini üret "+"
~nesne bit düzeyinde tersini al "~"
*nesne gösterdiğine eriş "*"
++nesne bir arttır "++"
--nesne bir azalt "--"

Örneğin ++ işlecini Süre için şöyle tanımlayabiliriz:

struct Süre
{
    int dakika;

    void opUnary(string işleç)()
        if (işleç == "++")
    {
        ++dakika;
    }
}

Süre nesneleri bu sayede artık ++ işleci ile arttırılabilirler:

    auto süre = Süre(20);
    ++süre;

Önceki değerli arttırma ve önceki değerli azaltma işleçleri olan nesne++ ve nesne-- kullanımları yüklenemez. O kullanımlardaki önceki değerleri derleyici otomatik olarak halleder. Örneğin nesne++ kullanımın eşdeğeri şudur:

    /* Önceki değer derleyici tarafından otomatik olarak
     * kopyalanır: */
    Süre __öncekiDeğer__ = süre;

    /* Tanımlanmış olan normal ++ işleci çağrılır: */
    ++süre;

    /* Daha sonra bütün ifadede __öncekiDeğer__ kullanılır. */
Yüklenebilen ikili işleçler

Aşağıdaki tabloda gösterilen değişmeli kavramı, işlecin sol tarafındaki ile sağ tarafındakinin yer değiştirmesi durumunda sonucun aynı olup olmadığını gösterir. Örneğin + işleci değişmeli bir işleçtir, çünkü x+y ile y+x işlemlerinin sonuçları aynıdır. Öte yandan, - değişmeli bir işleç değildir, çünkü x-y ve y-x işlemlerinin sonuçları farklıdır.

İşleçleri gruplandırmak için aşağıdaki tabloda işleçlerin türlerini de belirttim:


İşleç

Anlamı

Değişmeli
(Komütatif)

İşlev İsmi
Sağ Taraf
için
İşlev İsmi

Tür
+ topla evet opBinary opBinaryRight a
- çıkar hayır opBinary opBinaryRight a
* çarp evet opBinary opBinaryRight a
/ böl hayır opBinary opBinaryRight a
% kalanını hesapla hayır opBinary opBinaryRight a
^^ üssünü al hayır opBinary opBinaryRight a
& bit işlemi ve evet opBinary opBinaryRight b
| bit işlemi veya evet opBinary opBinaryRight b
^ bit işlemi ya da evet opBinary opBinaryRight b
<< sola kaydır hayır opBinary opBinaryRight b
>> sağa kaydır hayır opBinary opBinaryRight b
>>> işaretsiz sağa kaydır hayır opBinary opBinaryRight b
~ birleştir hayır opBinary opBinaryRight
== eşittir evet opEquals - m
!= eşit değildir evet opEquals - m
< öncedir hayır opCmp - s
<= sonra değildir hayır opCmp - s
> sonradır hayır opCmp - s
>= önce değildir hayır opCmp - s
= ata hayır opAssign - =
+= arttır hayır opOpAssign - =
-= azalt hayır opOpAssign - =
*= katını ata hayır opOpAssign - =
/= bölümünü ata hayır opOpAssign - =
%= kalanını ata hayır opOpAssign - =
^^= üssünü ata hayır opOpAssign - =
&= & sonucunu ata hayır opOpAssign - =
|= | sonucunu ata hayır opOpAssign - =
^= ^ sonucunu ata hayır opOpAssign - =
<<= << sonucunu ata hayır opOpAssign - =
>>= >> sonucunu ata hayır opOpAssign - =
>>>= >>> sonucunu ata hayır opOpAssign - =
~= sonuna ekle hayır opOpAssign - =
in içinde mi? hayır opBinary opBinaryRight m

Tabloda sağ taraf için olarak belirtilen işlev isimleri, nesne işlecin sağ tarafında da kullanılabildiğinde işletilir. Kodda şöyle bir ikili işleç kullanıldığını düşünelim:

    x işleç y

Derleyici hangi üye işlevi işleteceğine karar vermek için yukarıdaki ifadeyi şu iki üye işlev çağrısına dönüştürür:

    x.opBinary!"işleç"(y);
    y.opBinaryRight!"işleç"(x);

Eğer o işlevlerden birisi tanımlanmışsa, o işletilir. Değilse, ve işleç değişmeli bir işleçse, ayrıca şu işlevleri de dener:

    x.opBinaryRight!"işleç"(y);
    y.opBinary!"işleç"(x);

Normalde bu kadar karmaşık kuralları bilmek zorunda kalmayız. Çoğu durumda bu işlevlerden ismi Right ile sonlananların hiç tanımlanmasına gerek yoktur. (Bu durum in işlecinde tam tersidir: Onun için çoğunlukla opBinaryRight tanımlanır.)

Aşağıdaki örneklerde üye işlevleri tanımlarken parametre ismini sağdaki olarak seçtim. Bununla parametrenin işlecin sağındaki nesne olduğunu vurguluyorum:

    x işleç y

O ifade kullanıldığında, üye işlevin sağdaki ismindeki parametresi y olacaktır.

Aritmetik işleçler

Kendi türlerimizin aritmetik işlemlerde nasıl kullanıldıklarını belirler. Örneğin Süre nesnelerini birbirleriyle toplamak için opBinary işlecini "+" işleci için şöyle yükleyebiliriz:

struct Süre
{
    int dakika;

    Süre opBinary(string işleç)(in Süre sağdaki) const
        if (işleç == "+")
    {
        return Süre(dakika + sağdaki.dakika);
    }
}

O tanımdan sonra programlarımızda artık Süre nesnelerini + işleciyle toplayabiliriz:

    auto gitmeSüresi = Süre(10);
    auto dönmeSüresi = Süre(11);
    Süre toplamSüre;
    // ...
    toplamSüre = gitmeSüresi + dönmeSüresi;

Derleyici o ifadeyi dönüştürür ve perde arkasında gitmeSüresi nesnesi üzerinde bir üye işlev olarak çağırır:

    toplamSüre = gitmeSüresi.opBinary!"+"(dönmeSüresi);
Eşitlik karşılaştırmaları için opEquals

== ve != işleçlerinin davranışını belirler.

Mantıksal ifadelerde kullanıldığı için dönüş türü bool'dur.

opEquals üye işlevi bu işleçlerin ikisini de karşılar. O işlevi nesnelerin eşitliği için tanımlayınca, derleyici != işleci için onun tersini kullanır:

    x == y;
    x.opEquals(y);       // üsttekinin eşdeğeri

    x != y;
    !(x.opEquals(y));    // üsttekinin eşdeğeri

Normalde opEquals işlevini yapılar için tanımlamaya gerek yoktur; derleyici bütün üyelerin eşitliklerini sırayla otomatik olarak karşılaştırır ve nesnelerin eşit olup olmadıklarına öyle karar verir.

Bazen nesnelerin eşitliklerini kendimiz belirlemek isteyebiliriz. Örneğin bazı üyeler nesnenin eşit kabul edilmesi için önemsiz olabilirler, bir türün nesnelerinin eşit kabul edilmeleri özel bir kurala bağlı olabilir, vs.

Bunu göstermek için GününSaati sınıfı için dakika bilgisini gözardı eden bir opEquals tanımlayalım:

struct GününSaati
{
    int saat;
    int dakika;

    bool opEquals(const ref GününSaati sağdaki) const
    {
        return saat == sağdaki.saat;
    }
}
// ...
    assert(GününSaati(20, 10) == GününSaati(20, 59));

Eşitlik karşılaştırmasında yalnızca saat bilgisine bakıldığı için 20:10 ve 20:59 zamanları eşit çıkmaktadır. (Not: Bunun karışıklık doğuracağı açıktır; örnek olsun diye öylesine tanımladım.)

Sıra karşılaştırmaları için opCmp

Sıralama işleçleri nesnelerin öncelik/sonralık ilişkilerini belirler. Sıralama ile ilgili olan <, <=, >, ve >= işleçlerinin hepsi opCmp üye işlevi tarafından karşılanır.

Bu dört işleçten birisinin şu şekilde kullanıldığını kabul edelim:

    if (x işleç y) {

Derleyici o ifadeyi aşağıdakine dönüştürür ve o mantıksal ifadenin sonucunu kullanır:

    if (x.opCmp(y) işleç 0) {

Örnek olarak,

    if (x <= y) {

ifadesi şuna dönüştürülür:

    if (x.opCmp(y) <= 0) {

Kendi yazdığımız bu işlevin bu kurala göre doğru çalışabilmesi için işlevin şu değerleri döndürmesi gerekir:

Bundan anlaşılacağı gibi, opCmp'ın dönüş türü bool değil, int'tir.

GününSaati nesnelerinin sıralama ilişkilerini öncelikle saat değerine, saatleri eşit olduğunda da dakika değerlerine bakacak şekilde şöyle belirleyebiliriz:

    int opCmp(const ref GününSaati sağdaki) const
    {
        return (saat == sağdaki.saat
                ? dakika - sağdaki.dakika
                : saat - sağdaki.saat);
    }

Saat değerleri aynı olduğunda dakika değerlerinin farkı, saatler farklı olduğunda da saatlerin farkı döndürülüyor. Bu tanım, zaman sıralamasında soldaki nesne önce olduğunda eksi bir değer, sağdaki nesne önce olduğunda artı bir değer döndürür.

opCmp'un tanımlanmış olması, bu türün sıralama algoritmalarıyla da kullanılabilmesini sağlar. Sıralama algoritmalarının bir örneğini dizilerin .sort niteliğinde görmüştük. Aşağıdaki program 10 adet rasgele zaman değeri oluşturur ve onları dizinin .sort niteliği ile sıralar. Sıralamayı belirlerken nesneler karşılaştırılırken hep opCmp işletilir:

import std.random;
import std.stdio;
import std.string;

struct GününSaati
{
    int saat;
    int dakika;

    int opCmp(const ref GününSaati sağdaki) const
    {
        return (saat == sağdaki.saat
                ? dakika - sağdaki.dakika
                : saat - sağdaki.saat);
    }

    string toString()
    {
        return format("%02s:%02s", saat, dakika);
    }
}

void main()
{
    GününSaati[] zamanlar;

    foreach (i; 0 .. 10) {
        zamanlar ~= GününSaati(uniform(0, 24),
                               uniform(0, 60));
    }

    zamanlar.sort;

    writeln(zamanlar);
}

Dizinin .sort niteliği bizim tanımladığımız sıralama ilişkisini kullanır. Beklendiği gibi, çıktıdaki saat değerleri doğru sıradadır:

[03:40,04:10,09:06,10:03,10:09,11:04,13:42,16:40,18:03,21:08]
İşlev gibi çağırmak için opCall

İşlev çağırırken kullanılan parantezler de işleçtir. Bu işlecin davranışı da opCall üye işlevi tarafından belirlenir. Bu işleç sayesinde türün nesnelerini de işlev gibi kullanabiliriz:

    BirTür nesne;
    nesne();

O kodda nesne bir işlev gibi çağrılmaktadır.

Bunun bir örneği olarak bir doğrusal denklemde, verilen x değerlerine karşılık y değerlerini hesaplayan bir yapı düşünelim:

   y = ax + b

O hesaptaki a ve b'yi çarpan ve eklenen isimli üyeler olarak düşünürsek, y değerlerini opCall işlevi içinde şöyle hesaplayabiliriz:

struct DoğrusalDenklem
{
    double çarpan;
    double eklenen;

    double opCall(double x)
    {
        return çarpan * x + eklenen;
    }
}

Bir kere bunu tanımladıktan sonra, o sınıfın nesnelerini bir işlev gibi kullanarak verilen x değerlerine karşılık olan y değerlerini hesaplatabiliriz:

    DoğrusalDenklem denklem = { 1.2, 3.4 };
    double y = denklem(5.6);  // nesne işlev gibi kullanılıyor

Not: opCall işlevi tanımlanmış olan yapıları Tür(parametreler) yazımıyla kuramayız; çünkü o yazım da bir opCall çağrısı olarak kabul edilir. opCall'u tanımlanmış olan yapıların nesnelerini { } yazımıyla kurmamız gerekir.

İlk satırda nesne kurulurken denklemin çarpanı olarak 1.2, ekleneni olarak da 3.4 değerinin kullanılacağı belirleniyor. Bunun sonucunda denklem nesnesi, y = 1.2x + 3.4 denklemini ifade etmeye başlar. Ondan sonra nesneyi artık bir işlev gibi kullanarak, x değerlerini parametre olarak gönderiyor ve dönüş değeri olarak y değerlerini elde ediyoruz.

Bunun önemi, çarpan ve eklenen değerlerin baştan bir kere belirlenebilmesidir. Nesne o bilgiyi kendi içinde barındırır ve işlev gibi kullanıldığı zaman kullanır.

Başka çarpan ve eklenen değerleri ile kurulan bir nesneyi bu sefer de bir döngü içinde kullanan bir örnek:

    DoğrusalDenklem denklem = { 0.01, 0.4 };

    for (double x = 0.0; x <= 1.0; x += 0.125) {
        writefln("%f: %f", x, denklem(x));
    }

O da y = 0.01x + 0.4 denklemini x'in 0.0 ile 1.0 aralığındaki 0.125'in katı olan değerleri için hesaplar.

Dizi erişim işleçleri opIndex, opIndexAssign, opIndexUnary ve opIndexOpAssign

Bu işleçler, nesneyi nesne[indeks] şeklinde dizi gibi kullanma olanağı verirler.

opIndex erişim amacıyla kullanılır. Köşeli parantezler içinde kullanılan değerler işlevin parametreleri haline gelirler:

    birNot = bütünNotlar[3, 1];         // erişim
    birNot = bütünNotlar.opIndex(3, 1); // üsttekinin eşdeğeri

opIndexUnary, opUnary'nin eşdeğeridir; farkı, işlemin belirtilen indeksteki eleman üzerinde işleyecek olmasıdır:

    ++bütünNotlar[4, 0];                // arttırma
    bütünNotlar.opIndexUnary!"++"(4, 0);// üsttekinin eşdeğeri

opIndexAssign atama amacıyla kullanılabilir. İlk parametresi atanan değer, sonraki parametreleri de köşeli parantezler içinde kullanılan değerlerdir:

    bütünNotlar[1, 1] = 95;             // atama
    bütünNotlar.opIndexAssign(95, 1, 1);// üsttekinin eşdeğeri

opIndexOpAssign, opOpAssign'ın eşdeğeridir; farkı, atamalı işlemin belirtilen indeksteki eleman üzerinde işleyecek olmasıdır:

    bütünNotlar[2, 1] += 42;            // atamalı arttırma
    bütünNotlar.opIndexOpAssign!"+"(42, 2, 1);
                                        // üsttekinin eşdeğeri

Bütün bu işleçlerle kullanılan bir örnek olarak, bütün öğrencilerin bütün notlarını içeren bir yapıyı şöyle tanımlayabiliriz:

import std.stdio;

struct BütünÖğrencilerinNotları
{
    // 5 öğrenci için 2 not
    int[2][5] notlar;

    // Belirtilen notu döndürür
    int opIndex(int öğrenciİndeksi, int notİndeksi)
    {
        return notlar[öğrenciİndeksi][notİndeksi];
    }

    // Belirtilen notu bir arttırır
    void opIndexUnary(string işleç)(int öğrenciİndeksi,
                                    int notİndeksi)
        if (işleç == "++")
    {
        ++notlar[öğrenciİndeksi][notİndeksi];
    }

    // Belirtilen notu belirtilen öğrencinin notuna atar
    int opIndexAssign(int not,
                      int öğrenciİndeksi,
                      int notİndeksi)
    {
        return notlar[öğrenciİndeksi][notİndeksi] = not;
    }

    // Belirtilen öğrencinin notunu arttırır
    void opIndexOpAssign(string işleç)(int artış,
                                       int öğrenciİndeksi,
                                       int notİndeksi)
        if (işleç == "+")
    {
        notlar[öğrenciİndeksi][notİndeksi] += artış;
    }
}

void main()
{
    BütünÖğrencilerinNotları bütünNotlar;

    int birNot = bütünNotlar[3, 1];   // erişim
    ++bütünNotlar[4, 0];              // arttırma
    bütünNotlar[1, 1] = 95;           // atama
    bütünNotlar[2, 1] += 42;          // atamalı arttırma

    writeln(bütünNotlar.notlar);
}

Erişim satırını yalnızca göstermek amacıyla kullandım; programda bir amacı bulunmuyor. Diğer satırlar ise belirtilen öğrencilerin belirtilen notlarını etkilemiş oluyor:

[[0, 0], [0, 95], [0, 42], [0, 0], [1, 0]]
Dilim işleçleri opSlice, opSliceUnary, opSliceAssign, ve opSliceOpAssign

Yukarıdaki opIndex işleçlerine çok benzerler; farkları, nesneleri dilim işleciyle kullanma olanağı sağlamalarıdır. Bütün bu tanımların iki farklı kullanımı vardır: köşeli parantezlerin içinin boş olduğu kullanım ve köşeli parantezler içinde bir aralık belirtilen kullanım.

Bu sekiz kullanımın hangi işlevler tarafından yüklendiklerini bu işlevleri tanımlamadan gösteren bir örnek:

class BirTür
{
    int opSlice();                // nesne[]         kullanımı

    int opSlice(int i, int j);    // nesne[i .. j]   kullanımı

    void opSliceUnary(string işleç)()
        if (işleç == "++");       // ++nesne[] kullanımı

    void opSliceUnary(string işleç)(int i, int j)
        if (işleç == "++");       // ++nesne[i .. j] kullanımı

    int opSliceAssign(int değer); // nesne[] = değer kullanımı

    int opSliceAssign(int değer, int i, int j);
                            // nesne[i .. j] = değer kullanımı

    void opSliceOpAssign(string işleç)(int değer)
        if (işleç == "+"); // nesne[] += değer kullanımı

    void opSliceOpAssign(string işleç)(int değer, int i, int j)
        if (işleç == "+"); // nesne[i .. j] += değer kullanımı
}

void main()
{
    BirTür nesne;

    int i;
    int değer;

    i = nesne[];
    i = nesne.opSlice();                // üsttekinin eşdeğeri

    i = nesne[3 .. 4];
    i = nesne.opSlice(3, 4);            // üsttekinin eşdeğeri

    ++nesne[];
    nesne.opSliceUnary!"++"();          // üsttekinin eşdeğeri

    ++nesne[3 .. 4];
    nesne.opSliceUnary!"++"(3, 4);      // üsttekinin eşdeğeri

    nesne[] = değer;
    nesne.opSliceAssign(değer);         // üsttekinin eşdeğeri

    nesne[3 .. 4] = değer;
    nesne.opSliceAssign(değer, 3, 4);   // üsttekinin eşdeğeri

    nesne[] += değer;
    nesne.opSliceOpAssign!"+"(değer);   // üsttekinin eşdeğeri

    nesne[3 .. 4] += değer;
    nesne.opSliceOpAssign!"+"(değer, 3, 4);
                                        // üsttekinin eşdeğeri
}
Tür dönüşümü işleci opCast

Elle açıkça yapılan tür dönüşümünü belirleyen opCast, dönüştürülecek her tür için ayrı ayrı yüklenebilir ve o tür opCast'in dönüş türü olarak yazılır. Bu işleç de şablon olarak tanımlanır ama kalıbı farklıdır: hangi dönüşümün tanımlanmakta olduğu (Tür : dönüştürülecek_tür) söz dizimiyle belirtilir:

    dönüştürülecek_tür opCast(Tür : dönüştürülecek_tür)()
    {
        // ...
    }

Yine şimdilik bir kalıp olarak kabul etmenizi isteyeceğim bu söz dizimini de daha sonra şablonlar dersinde anlatacağım.

Süre'nin saat ve dakikadan oluşan bir tür olduğunu kabul edelim. Bu türün nesnelerini double türüne dönüştüren işlev şöyle tanımlanabilir:

import std.stdio;

struct Süre
{
    int saat;
    int dakika;

    double opCast(Tür : double)()
    {
        return saat + (cast(double)dakika / 60);
    }
}

void main()
{
    auto süre = Süre(2, 30);
    double kesirli = cast(double)süre;
    writeln(kesirli);
}

Açıkça tür dönüşümü istenen satırda derleyici üye işlevi şöyle çağırır:

    double kesirli = süre.opCast!double();

double türüne dönüştüren işleç, iki saat otuz dakikaya karşılık 2.5 değerini üretmektedir:

2.5
Sevk işleci opDispatch

Nesnenin var olmayan bir üyesine erişildiğinde çağrılacak olan üye işlevdir. Var olmayan üyelere erişim, bu işlece sevk edilir.

Var olmayan üyenin ismi, opDispatch'e bir şablon parametresi olarak gelir. (Not: Şablonları daha sonraki bir derste anlatacağım.)

Bu işleci çok basit olarak gösteren bir örnek:

import std.stdio;

struct BirTür
{
    void opDispatch(string isim, T)(T parametre)
    {
        writefln("BirTür.opDispatch - isim: %s, değer: %s",
                 isim, parametre);
    }
}

void main()
{
    BirTür nesne;
    nesne.varOlmayanİşlev(42);
    nesne.varOlmayanBaşkaİşlev(100);
}

Var olmayan üyelerine eriştiğimiz halde derleme hatası almayız. O çağrılar, opDispatch işlevinin çağrılmasını sağlarlar. Birinci şablon parametresi işlevin ismidir. Çağrılan noktada kullanılan parametreler de opDispatch'in parametreleri haline gelirler:

BirTür.opDispatch - isim: varOlmayanİşlev, değer: 42
BirTür.opDispatch - isim: varOlmayanBaşkaİşlev, değer: 100
İçerme sorgusu için opBinary!"in"

Eşleme tablolarından tanıdığımız in işlecini nesneler için de tanımlama olanağı sağlar.

Diğer işleçlerden farklı olarak, bu işleçte nesnenin sağda yazıldığı durum daha doğaldır:

        if (zaman in öğleTatili) {

O yüzden bu işleç için daha çok opBinaryRight!"in" yüklenir ve derleyici de perde arkasında o üye işlevi çağırır:

                                        // üsttekinin eşdeğeri
        if (öğleTatili.opBinaryRight!"in"(zaman)) {

Bu işleci aşağıdaki örnekte kullanıyorum.

Örnek

Bu örnek, daha önce gördüğümüz Süre ve GününSaati yapılarına ek olarak bir ZamanAralığı yapısı tanımlıyor. Bu yapı için tanımlanan in işleci, belirli bir zamanın belirli bir aralıkta olup olmadığını belirtiyor.

Bu örnekte de yalnızca gerektiği kadar üye işlev kullandım.

GününSaati nesnesinin for döngüsünde nasıl temel türler kadar rahat kullanıldığına özellikle dikkat edin. O döngü, işleç yüklemenin yararını güzelce gösteriyor.

import std.stdio;
import std.string;

struct Süre
{
    int dakika;
}

struct GününSaati
{
    int saat;
    int dakika;

    void opOpAssign(string işleç)(in Süre süre)
        if (işleç == "+")
    {
        dakika += süre.dakika;

        saat += dakika / 60;
        dakika %= 60;
        saat %= 24;
    }

    int opCmp(const ref GününSaati sağdaki) const
    {
        return (saat == sağdaki.saat
                ? dakika - sağdaki.dakika
                : saat - sağdaki.saat);
    }

    string toString()
    {
        return format("%02s:%02s", saat, dakika);
    }
}

struct ZamanAralığı
{
    GününSaati baş;
    GününSaati son;    // aralığın dışında kabul edilir

    bool opBinaryRight(string işleç)
                            (const ref GününSaati zaman) const
        if (işleç == "in")
    {
        return (zaman >= baş) && (zaman < son);
    }
}

void main()
{
    auto öğleTatili = ZamanAralığı(GününSaati(12, 00),
                                   GününSaati(13, 00));

    for (auto zaman = GününSaati(11, 30);
         zaman < GününSaati(13, 30);
         zaman += Süre(15)) {

        if (zaman in öğleTatili) {
            writeln(zaman, " öğle tatilinde");

        } else {
            writeln(zaman, " öğle tatili dışında");
        }
    }
}

Programın çıktısı:

11:30 öğle tatili dışında
11:45 öğle tatili dışında
12:00 öğle tatilinde
12:15 öğle tatilinde
12:30 öğle tatilinde
12:45 öğle tatilinde
13:00 öğle tatili dışında
13:15 öğle tatili dışında
Problemler
  1. GününSaati yapısının opCmp işlevini zamanların ters sırada sıralanmalarını sağlayacak şekilde değiştirin.
  2. Yukarıda kullanılan ZamanAralığı yapısı için >>= ve <<= işleçlerini tanımlayın. Normalde bit işlemleri için kullanılan bu işleçler, zaman aralığını belirli bir Süre kadar sağa ve sola kaydırsınlar:
  3.     aralık >>= Süre(10);         // bütün aralık 10 dakika
                                     // sonrasına kaysın
    

    <<= işleci için Üye İşlevler dersinin çözümlerinde gördüğümüz GününSaati.azalt işlevinden yararlanmak isteyebilirsiniz. Onun ismini değiştirerek GününSaati nesneleri için -= işleci olarak kullanabilirsiniz.

  4. Bu kullanımı şüpheli olsa da, ~ işlecini ZamanAralığı ve GününSaati nesnelerinin aşağıdaki iki kullanımları için yükleyin:
  5.     ZamanAralığı aralık;
        GününSaati zaman;
        // ...
        yeniAralık = aralık ~ zaman;
    

    şeklindeki kullanımda yeni aralığın sonu zaman'a eşit olsun, ve

        yeniAralık = zaman ~ aralık;
    

    şeklindeki kullanımda da yeni aralığın başı zaman'a eşit olsun.

... çözümler