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.)
kurma: [construct], yapı veya sınıf nesnesini kullanılabilir duruma getirmek
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

Not: D'de işleç yükleme olanağı, bu ders yazıldıktan sonra değişti. Aşağıda anlatılanların bir bölümü yakında emekliye ayrılacak. Bu dersi, yeni işleç yükleme olanağını içerecek şekilde değiştireceğim.

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 üye işlevi göstererek:

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 olunca, 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 kadarıyla, benzer bir işlemi yapılar için gerçekleştirmek için bir üye işlev kullanmamız gerekir:

    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ürler 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 daha 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 opAddAssign ismiyle tanımlamak yeterlidir:

struct GününSaati
{
// ...
    void opAddAssign(in Süre süre)
    {
        dakika += süre.dakika;

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

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

    yemekZamanı += Süre(10);
    yemekZamanı.opAddAssign(Süre(10));  // üsttekinin eşdeğeri
Uyarı

İş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şlevi 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 sizin ve başka programcıların kafalarını karıştıracağını ve programda hatalara neden olacağını aklınızda tutun.

Tekli işleç

Yalnızca tek bir nesneyle çalışan 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 ise sağındakinin değerini solundakine atar.

Bu derste yüklenebilen bütün işleçler için örnekler 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
İşleç Anlamı İşlev İsmi
-nesne ters işaretlisini üret opNeg
+nesne aynı işaretlisini üret opPos
~nesne bit düzeyinde tersini al opCom
*nesne gösterdiğine eriş opStar
++nesne bir arttır bkz. += 1
nesne++ önceki değerle bir arttır opPostInc
--nesne bir azalt bkz. -= 1
nesne-- önceki değerle bir azalt opPostDec
cast(Tür)nesne tür dönüştür opCast

Yukarıdaki işlev isimleri, işlecin yüklenmesi için hangi isimde üye işlev tanımlanması gerektiğini belirtir. Derleyici, işleç kullanımı yerine bu üye işlevi çağırır. Bunu, önceki derste gördüğümüz opAssign üye işlevi ile şöyle gösterebiliriz:

    zaman = başkaZaman;
    zaman.opAssign(başkaZaman);     // üsttekinin eşdeğeri

++nesne şeklindeki kullanım yüklenemez. Onun yerine, aşağıda gösterilen += işleci yüklenir ve derleyici o işlevi 1 değeriyle çağırır. Benzer şekilde, --nesne kullanımı için de -= işleci 1 değeriyle çağrılır.

Elle açıkça yapılan tür dönüşümünü belirleyen opCast, tek bir tür için yüklenebilir ve o tür opCast'in dönüş türü olarak yazılır. Örneğin Süre nesnelerini int türüne dönüştürmek için:

struct Süre
{
    int dakika;

    int opCast()
    {
        return dakika;
    }
}

Açıkça tür dönüşümü istendiğinde:

    auto süre = Süre(42);
    auto tamsayıOlarak = cast(int)süre;

Derleyici son satır için üye işlevi şöyle çağırır:

    auto tamsayıOlarak = süre.opCast();
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 opAdd opAdd_r a
- çıkar hayır opSub opSub_r a
* çarp evet opMul opMul_r a
/ böl hayır opDiv opDiv_r a
% kalanını hesapla hayır opMod opMod_r a
^^ üssünü al hayır opPow opPow_r a
& bit işlemi ve evet opAnd opAnd_r b
| bit işlemi veya evet opOr opOr_r b
^ bit işlemi ya da evet opXor opXor_r b
<< sola kaydır hayır opShl opShl_r b
>> sağa kaydır hayır opShr opShr_r b
>>> işaretsiz sağa kaydır hayır opUShr opUShr_r b
~ birleştir hayır opCat opCat_r
== eşittir evet opEquals - m
!= eşit değildir evet opEquals - m
< öncedir evet opCmp - s
<= sonra değildir evet opCmp - s
> sonradır evet opCmp - s
>= önce değildir evet opCmp - s
= ata hayır opAssign - =
+= arttır hayır opAddAssign - =
-= azalt hayır opSubAssign - =
*= katını al hayır opMulAssign - =
/= kadarına böl hayır opDivAssign - =
%= kalanını ata hayır opModAssign - =
&= & sonucunu ata hayır opAndAssign - =
|= | sonucunu ata hayır opOrAssign - =
^= ^ sonucunu ata hayır opXorAssign - =
<<= << sonucunu ata hayır opShlAssign - =
>>= >> sonucunu ata hayır opShrAssign - =
>>>= >>> sonucunu ata hayır opUShrAssign - =
~= sonuna ekle hayır opCatAssign - =
in içinde mi? hayır opIn opIn_r 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.opİşlevİsmi(y);
    y.opİşlevİsmi_r(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.opİşlevİsmi_r(y);
    y.opİşlevİsmi(x);

Normalde bu kadar karmaşık kuralları bilmek zorunda kalmayız. Çoğu durumda bu işlevlerden ismi _r ile sonlananların hiç tanımlanmasına gerek yoktur.

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 opAdd işlecini şöyle yükleyebiliriz:

struct Süre
{
    int dakika;

    Süre opAdd(in Süre sağdaki) const
    {
        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 sağdaki 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.opAdd(dönmeSüresi);

Eğer Süre.opAssign işlevini de tanımlamış olsaydık, bütün ifade şuna dönüşürdü:

    toplamSüre.opAssign(gitmeSüresi.opAdd(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.

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, sağdaki nesne önce olduğunda artı bir değer döndürür.

opCmp'un tanımlanmış olması, türümüzün sıralama algoritmalarıyla da kullanılabilmelerini sağlar. Sıralama algoritmalarının bir örneğini dizilerin .sort niteliğinde görmüştük. Aşağıdaki program 10 adet rastgele zaman değeri oluşturur ve onları dizinin .sort niteliği ile sıralar:

import std.random;
import std.cstream;
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;

    dout.writefln(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. 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 tek 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) {
        dout.writefln("%f: %f", x, denklem(x));
    }
Dizi erişim işleçleri opIndex ve opIndexAssign

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

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];
    birNot = bütünNotlar.opIndex(3, 1); // üsttekinin eşdeğeri

opIndexAssign ise 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;
    bütünNotlar.opIndexAssign(95, 1, 1);// üsttekinin eşdeğeri

Bir örnek olarak, bütün öğrencilerin bütün notlarını içeren bir yapıyı şöyle tanımlayabiliriz:

import std.cstream;

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

    int opIndex(int öğrenciİndeksi, int notİndeksi)
    {
        return notlar[öğrenciİndeksi][notİndeksi];
    }

    int opIndexAssign(int not,
                      int öğrenciİndeksi,
                      int notİndeksi)
    {
        return notlar[öğrenciİndeksi][notİndeksi] = not;
    }
}

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

    int birNot = bütünNotlar[3, 1];   // erişim
    bütünNotlar[1, 1] = 95;           // atama

    dout.writefln(bütünNotlar.notlar);
}

Erişim satırını yalnızca göstermek amacıyla kullandım; programda bir amacı bulunmuyor. Atama yapan satır ise 1 indeksli öğrencinin 1 indeksli notunu değiştirmektedir:

[[0,0],[0,95],[0,0],[0,0],[0,0]]
Dilim işleçleri opSlice ve opSliceAssign

Nesneleri dilim işleciyle kullanma olanağı sağlarlar. İkisinin de 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 dört kullanımın hangi işlevler tarafından yüklendiğini, bu işlevleri tanımlamadan gösteren bir örnek:

class BirTür
{
    int opSlice();                // nesne[]         kullanımı
    int opSlice(int x, int y);    // nesne[i .. j]   kullanımı

    int opSliceAssign(int değer); // nesne[] = değer kullanımı
    int opSliceAssign(int değer, int x, int y);
                            // 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[] = 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
}
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.cstream;

struct BirTür
{
    void opDispatch(string isim, T)(T parametre)
    {
        dout.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 opIn

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 opIn_r yüklenir ve derleyici de perde arkasında o üye işlevi çağırır:

        if (öğleTatili.opIn_r(zaman)) { // üsttekinin eşdeğeri

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.cstream;
import std.string;

struct Süre
{
    int dakika;
}

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

    void opAddAssign(in Süre süre)
    {
        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 opIn_r(const ref GününSaati zaman) const
    {
        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) {
            dout.writefln(zaman, " öğle tatilinde");

        } else {
            dout.writefln(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