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

gösterge: [pointer], bir değişkeni gösteren değişken
iç tanım: [nested definition], iç kapsamda tanımlanmış olan
kapama: [closure], işlemi ve işlediği kapsamı bir arada saklayan program yapısı
sarma: [encapsulation], üyelere dışarıdan erişimi kısıtlamak
... bütün sözlük



İngilizce Kaynaklar


Diğer




İç İşlevler, Yapılar, ve Sınıflar

İşlevler, yapılar, ve sınıflar iç kapsamlarda tanımlanabilirler. Bu hem isimlerin daha dar kapsamlarda geçerli olmalarını ve böylece bir anlamda o isimlerin sarmalanmalarını sağlar hem de İşlev Göstergeleri, İsimsiz İşlevler, ve Temsilciler bölümünde gördüğümüz kapamaların başka bir gerçekleştirmesidir.

Bir örnek olarak, aşağıdaki dışİşlev() işlevinin kapsamında bir işlev, bir yapı, ve bir de sınıf tanımlanmaktadır:

void dışİşlev(int parametre) {
    int yerel;

    void içİşlev() {
        yerel = parametre * 2;
    }

    struct İçYapı {
        void üyeİşlev() {
            yerel /= parametre;
        }
    }

    class İçSınıf {
        void üyeİşlev() {
            yerel += parametre;
        }
    }

    // İşlev içindeki kullanımları:

    içİşlev();

    auto y = İçYapı();
    y.üyeİşlev();

    auto s = new İçSınıf();
    s.üyeİşlev();
}

void main() {
    dışİşlev(42);
}

Beklenebileceği gibi, iç tanımlar dış kapsamlarındaki değişkenlere erişebilirler. Örneğin, yukarıdaki koddaki iç tanımların üçü de parametre ve yerel adlı değişkenlere erişebilmektedir.

İşlev içinde tanımlanan değişkenlerde olduğu gibi, işlev içinde tanımlanan isimler de yalnızca tanımlandıkları kapsamda geçerlidir. Örneğin; içİşlev(), İçYapı, ve İçSınıf isimleri main() içinde kullanılamaz:

void main() {
    auto a = İçYapı();             // ← derleme HATASI
    auto b = dışİşlev.İçYapı();    // ← derleme HATASI
}

Ancak, isimleri kullanılamasalar da iç tanımlar başka kapsamlarda kullanılabilirler. Örneğin, bir çok Phobos algoritması görevini kendi içinde tanımladığı bir yapı aracılığıyla gerçekleştirir.

Bunun bir örneğini görmek için kendisine verilen dilimi bir baştan bir sondan tüketerek kullanan bir işlev tanımlayalım:

import std.stdio;
import std.array;

auto baştanSondan(T)(T[] dilim) {
    bool baştan_mı = true;

    struct BaştanSondanAralığı {
        bool empty() @property const {
            return dilim.empty;
        }

        T front() @property const {
            return baştan_mı ? dilim.front : dilim.back;
        }

        void popFront() {
            if (baştan_mı) {
                dilim.popFront();
                baştan_mı = false;

            } else {
                dilim.popBack();
                baştan_mı = true;
            }
        }
    }

    return BaştanSondanAralığı();
}

void main() {
    auto a = baştanSondan([ 1, 2, 3, 4, 5 ]);
    writeln(a);
}

Her ne kadar ismine erişemese de, main() baştanSondan() işlevinin kurduğu ve döndürdüğü iç yapı nesnesini kullanabilir:

[1, 5, 2, 4, 3]

Not: İsimlerinin söylenemiyor olması Harry Potter karakterlerinden Voldemort'u çağrıştırdığından bu çeşit türlere Voldemort türü denir.

Dikkat ederseniz, baştanSondan() işlevinin döndürdüğü iç yapının hiçbir üyesi bulunmamaktadır. O yapı görevini yalnızca işlev parametresi olan dilim'i ve yerel değişken olan baştan_mı'yı kullanarak gerçekleştirmektedir. Bu değişkenlerin normalde işlevden çıkılırken sonlanacak olan yaşamları iç yapı nesnesi yaşadığı sürece uzatılır. Bu; İşlev Göstergeleri, İsimsiz İşlevler, ve Temsilciler bölümünde gördüğümüz kapsam saklama kavramının aynısıdır: İşlevlerden döndürülen iç tanımlar tanımlandıkları kapsamların yaşam süreçlerini kendileri yaşadıkları sürece uzatırlar ve böylece fonksiyonel programlamadaki kapama kavramını oluştururlar.

Kapama gerekmeyen durumlarda static

Tanımlandıkları kapsamı da barındırdıklarından iç tanımlar modül düzeyinde tanımlanmış olan benzerlerinden daha masraflıdır. Ek olarak, bu türlerin nesneleri işledikleri kapsamın hangisi olduğunu bildiren gizli bir kapsam göstergesi de barındırmak zorundadırlar. İç tanım nesneleri bu yüzden daha fazla yer de kaplarlar. Örneğin, aynı sayıda üye değişkene sahip oldukları halde aşağıdaki iki yapının boyutları farklıdır:

import std.stdio;

struct Dış {
    int i;

    void işlev() {
    }
}

void foo() {
    struct İç {
        int i;

        void işlev() {
        }
    }

    writefln("Dıştaki %s bayt, içteki %s bayt",
             Dış.sizeof, İç.sizeof);
}

void main() {
    foo();
}

Büyüklükler farklı ortamlarda farklı olabilir. Benim ortamımdaki çıktısı aşağıdaki gibi:

Dıştaki 4 bayt, içteki 16 bayt

İç tanımlar bazen yalnızca kodu olabildiğince yerel tanımlamak amacıyla kullanılırlar; kapsamdaki değişkenlere erişmeyle ve dolayısıyla kapama oluşturmayla ilgileri yoktur. Getirdikleri masraf böyle durumlarda gereksiz olacağından iç tanımların normal tanımlara eşdeğer olmaları istendiğinde static anahtar sözcüğü kullanılır. Bunun doğal sonucu olarak static iç tanımlar kapsamdaki değişkenlere erişemezler:

void dışİşlev(int parametre) {
    static class İçSınıf {
        int i;

        this() {
            i = parametre;    // ← derleme HATASI
        }
    }
}

Bir iç sınıf nesnesinin kapsam göstergesi .outer niteliği ile void* türünde elde edilebilir. Örneğin, aynı kapsamda oluşturulan iki sınıf değişkeninin kapsam göstergeleri bekleneceği gibi aynıdır:

void foo() {
    class C {
    }

    auto a = new C();
    auto b = new C();

    assert(a.outer is b.outer);
}

Sınıf içinde tanımlanan sınıflarda kapsam göstergesinin türü void* değil, dış sınıfın türüdür. Bunu biraz aşağıda göreceğiz.

Sınıf içinde tanımlanan sınıflar

Bir sınıf başka bir sınıf içinde tanımlandığında iç sınıfın kapsamı dış sınıf nesnesinin kendisidir.

Bu çeşit iç sınıf nesneleri özel this.new söz dizimi ile oluşturulurlar. Dış kapsamı oluşturan nesneye gerektiğinde this.outer ile erişilebilir:

class Dış {
    int dışÜye;

    class İç {
        int işlev() {
            /* İç sınıf dış sınıfın üyelerine erişebilir. */
            return dışÜye * 2;
        }

        Dış dışNesne() {
            /* İç nesne kendi kapsamı olan dış nesnesine
             * 'outer' anahtar sözcüğüyle erişebilir. Bu
             * örnekte yalnızca dönüş değeri olarak
             * kullanıyor.  */
            return this.outer;
        }
    }

    İç algoritma() {
        /* Dış kendisini kapsam olarak kullanacak olan bir İç
         * nesnesini özel 'this.new' söz dizimi ile kurar. */
        return this.new İç();
    }
}

void main() {
    auto dış = new Dış();

    /* Dış'ın bir işlevinin bir İç nesnesi döndürmesi: */
    auto iç = dış.algoritma();

    /* Döndürülen nesnenin kullanılması: */
    iç.işlev();

    /* Doğal olarak 'iç'in kapsamı 'dış'tır: */
    assert(iç.dışNesne() is dış);
}

Bu örnekteki this.new ve this.outer söz dizimleri yerine .new ve .outer var olan değişkenlere de uygulanabilir:

    auto dış = new Dış();
    auto iç = dış.new Dış.İç();
    auto dış2 = iç.outer;
Özet