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

bayt sırası: [endianness], veriyi oluşturan baytların bellekte sıralanma düzeni
çokuzlu: [tuple], bir kaç parçanın diziye benzer biçimde bir araya gelmesinden oluşan yapı
gösterge: [pointer], bir değişkeni gösteren değişken
hata ayıklama: [debug], programın hatalarını bulma ve giderme
indeks: [index], topluluk elemanlarına erişmek için kullanılan bilgi
statik: [static], derleme zamanında belirli olan
sürüm: [version], programın, olanaklarına göre farklar içeren hali
... bütün sözlük



İngilizce Kaynaklar


Diğer




Koşullu Derleme

Programın bazı bölümlerinin belirli koşullara bağlı olarak farklı derlenmesi veya hiç derlenmemesi istenebilir. D'nin koşullu derleme olanakları bu konuda kullanılır.

Bu koşullar yalnızca derleme zamanında değerlendirilirler; programın çalışması sırasında etkileri yoktur. Çalışma zamanında etkili olan if, for, while gibi D olanakları koşullu derleme olanakları değildir.

Aslında önceki bölümlerde koşullu derleme olarak kabul edilebilecek olanaklarla karşılaşmıştık:

Yukarıdakiler programın doğruluğunu arttırma amacına yönelik yardımcı olanaklar olarak görülebilir. Derleyici seçeneklerine bağlı olarak kullanılıp kullanılmamaları, programın asıl davranışını zaten değiştirmemelidir.

D'nin koşullu derlemeyi destekleyen ve bütünüyle bu amaç için tasarlanmış olan başka olanakları da vardır:

is ifadesini bir sonraki bölümde göreceğiz.

debug

Program geliştirme aşamasında yararlı olan bir olanak debug belirtecidir. Bu belirteçle işaretlenmiş olan ifadeler ve deyimler yalnızca derleyiciye -debug seçeneği verildiğinde etkilidir:

debug koşullu_derlenen_bir_ifade;

debug {
    // ... koşullu derlenen ifadeler ve deyimler ...

} else {
    // ... diğer durumda derlenen ifadeler ve deyimler ...
}

else bloğu isteğe bağlıdır.

Yukarıdaki tek ifade de blok içindeki ifadeler de ancak -debug derleyici seçeneği etkin olduğunda derlenir.

Şimdiye kadarki programların hemen hemen hepsinde programın nasıl işlediğini gösteren ve çıkışa "ekliyorum", "çıkartıyorum" gibi mesajlar yazdıran satırlar kullandık. Algoritmaların işleyişlerini böylece görsel hale getiriyor ve olası hatalarını bulabiliyorduk. "debug", hata gidermek anlamına gelir ve bu konuda yararlıdır.

Bunun bir örneği olarak Şablonlar bölümünde gördüğümüz ikiliAra işlevine bakalım. O algoritmanın açıklama satırlarını çıkartıyorum ve bilerek hatalı olarak yazıyorum:

import std.stdio;

// DİKKAT! Bu algoritma hatalıdır
size_t ikiliAra(const int[] değerler, int değer) {
    if (değerler.length == 0) {
        return size_t.max;
    }

    immutable ortaNokta = değerler.length / 2;

    if (değer == değerler[ortaNokta]) {
        return ortaNokta;

    } else if (değer < değerler[ortaNokta]) {
        return ikiliAra(değerler[0 .. ortaNokta], değer);

    } else {
        return ikiliAra(değerler[ortaNokta + 1 .. $], değer);
    }
}

void main() {
    auto sayılar = [ -100, 0, 1, 2, 7, 10, 42, 365, 1000 ];

    auto indeks = ikiliAra(sayılar, 42);
    writeln("Konum: ", indeks);
}

Yukarıdaki program 42'nin aslında 6 olan konumunu yanlış bildirir:

Konum: 1    ← yanlış sonuç

Bu hatayı bulmanın bir yolu, işlevin önemli noktalarına işlemler hakkında bilgiler veren satırlar eklemektir:

size_t ikiliAra(const int[] değerler, int değer) {
    writeln(değerler, " içinde ", değer, " arıyorum");

    if (değerler.length == 0) {
        writeln(değer, " bulunamadı");
        return size_t.max;
    }

    immutable ortaNokta = değerler.length / 2;

    writeln("bakılan konum: ", ortaNokta);

    if (değer == değerler[ortaNokta]) {
        writeln(değer, ", ", ortaNokta, " konumunda bulundu");
        return ortaNokta;

    } else if (değer < değerler[ortaNokta]) {
        writeln("ilk yarıda olması gerek");
        return ikiliAra(değerler[0 .. ortaNokta], değer);

    } else {
        writeln("son yarıda olması gerek");
        return ikiliAra(değerler[ortaNokta + 1 .. $], değer);
    }
}

Programın şimdiki çıktısı algoritmanın işleyiş adımlarını da gösterir:

[-100,0,1,2,7,10,42,365,1000] içinde 42 arıyorum
bakılan konum: 4
son yarıda olması gerek
[10,42,365,1000] içinde 42 arıyorum
bakılan konum: 2
ilk yarıda olması gerek
[10,42] içinde 42 arıyorum
bakılan konum: 1
42, 1 konumunda bulundu
Konum: 1

Hatanın bu çıktıdan yararlanılarak bulunduğunu ve giderildiğini varsayalım. Hata giderildikten sonra artık writefln satırlarına gerek kalmaz, üstelik silinmeleri gerekir. Buna rağmen, o satırları silmek de bir israf olarak görülebilir çünkü belki de ileride tekrar gerekebilirler.

Onun yerine, bu satırların başına debug anahtar sözcüğü yazılabilir:

        debug writeln(değer, " bulunamadı");

O satırlar artık yalnızca -debug derleyici seçeneği kullanıldığında etkin olacaktır:

dmd deneme.d -ofdeneme -w -debug

Böylece programın normal işleyişi sırasında çıktıya hiçbir bilgi yazdırılmayacak, bir hata görüldüğünde ise -debug kullanılarak algoritmanın işleyişi hakkında bilgi alınabilecektir.

debug(isim)

debug belirtecinin çok yerde kullanılması durumunda programın çıktısı çok kalabalıklaşabilir. Böyle durumlarda debug belirteçlerine isimler verebilir ve onların yalnızca komut satırında belirtilenlerinin etkinleşmelerini sağlayabiliriz:

        debug(ikili_arama) writeln(değer, " bulunamadı");

İsimli debug belirteçlerini etkinleştirmek için komut satırında -debug=isim yazılır:

dmd deneme.d -ofdeneme -w -debug=ikili_arama

İsimli debug belirteçleri de birden fazla ifade için kullanılabilir:

    debug(ikili_arama) {
        // ... koşullu derlenen ifadeler ve deyimler ...
    }

Aynı anda birden çok isimli debug belirteci de belirtilebilir:

$ dmd deneme.d -ofdeneme -w -debug=ikili_arama -debug=yigin_yapisi

O durumda hem ikili_arama, hem de yigin_yapisi isimli debug blokları etkin olur.

debug(düzey)

Bazen debug belirteçlerine isimler vermek yerine, hata ayıklama düzeylerini belirleyen sayısal değerler verilebilir. Örneğin, artan her düzey daha derinlemesine bilgi elde etmek için yararlı olabilir:

debug import std.stdio;

void birİşlev(string dosyaİsmi, int[] sayılar) {
    debug(1) writeln("birİşlev işlevine girildi");

    debug(2) {
        writeln("işlev parametreleri: ");
        writeln("  isim: ", dosyaİsmi);

        foreach (i, sayı; sayılar) {
            writefln("  %4s: %s", i, sayı);
        }
    }

    // ... asıl işlemler ...
}

Derleyiciye bildirilen debug düzeyi, o düzey ve daha düşük olanlarının etkinleşmesini sağlar:

$ dmd deneme.d -ofdeneme -w -debug=1
$ ./deneme 
birİşlev işlevine girildi

Daha derinlemesine bilgi almak istendiğinde:

$ dmd deneme.d -ofdeneme -w -debug=2
$ ./deneme 
birİşlev işlevine girildi
işlev parametreleri: 
  isim: deneme.txt
     0: 10
     1: 4
     2: 100
version(isim), ve version(düzey)

version, debug olanağına çok benzer ve kod içinde aynı biçimde kullanılır:

    version(denemeSürümü) /* ... bir ifade ... */;

    version(okulSürümü) {
        // ... okullara satılan sürümle ilgili ifadeler ...

    } else {
        // ... başka sürümlerle ilgili ifadeler ...
    }

    version(1) birDeğişken = 5;

    version(2) {
        // ... sürüm 2 ile ilgili bir olanak ...
    }

Bütünüyle aynı biçimde çalışıyor olsa da, debug'dan farkı, programın farklı sürümlerini oluşturma amacıyla kullanılmasıdır.

Yine debug'da olduğu gibi, aynı anda birden fazla version bloğu etkinleştirilebilir:

$ dmd deneme.d -ofdeneme -w -version=kayit -version=hassas_hesap

Bazı version isimleri hazır olarak tanımlıdır. Tam listesini Conditional Compilation sayfasında bulacağınız bu isimleri aşağıdaki tabloda özetliyorum:

Öntanımlı version belirteçleri
Derleyici DigitalMars GNU LDC SDC
İşletim sistemi Windows Win32 Win64 linux OSX Posix FreeBSD OpenBSD NetBSD DragonFlyBSD BSD Solaris AIX Haiku SkyOS SysV3 SysV4 Hurd
Mikro işlemci sonculluğuLittleEndian BigEndian
Derleyici seçenekleri D_Coverage D_Ddoc D_InlineAsm_X86 D_InlineAsm_X86_64 D_LP64 D_PIC D_X32 D_HardFloat D_SoftFloat D_SIMD D_Version2 D_NoBoundsChecks unittest assert
Mikro işlemci mimarisi X86 X86_64
Platform Android Cygwin MinGW ARM ARM_Thumb ARM_Soft ARM_SoftFP ARM_HardFP ARM64 PPC PPC_SoftFP PPC_HardFP PPC64 IA64 MIPS MIPS32 MIPS64 MIPS_O32 MIPS_N32 MIPS_O64 MIPS_N64 MIPS_EABI MIPS_NoFloat MIPS_SoftFloat MIPS_HardFloat SPARC SPARC_V8Plus SPARC_SoftFP SPARC_HardFP SPARC64 S390 S390X HPPA HPPA64 SH SH64 Alpha Alpha_SoftFP Alpha_HardFP
... ...

İki tane de özel version ismi vardır:

O tanımlardan yararlanarak programınızın farklı olanaklarla derlenmesini sağlayabilirsiniz. Kullanım örneği olarak std.ascii modülünde tanımlı olan newline'a bakalım. Satır sonu anlamına gelen kodları belirleyen newline dizisi, üzerinde derlenmekte olduğu işletim sistemine göre farklı kodlardan oluşmaktadır:

version(Windows) {
    immutable newline = "\r\n";

} else version(Posix) {
    immutable newline = "\n";

} else {
    static assert(0, "Unsupported OS");
}
debug'a ve version'a isim atamak

debug ve version'a sanki değişkenmişler gibi isim atanabilir. Değişkenlerden farklı olarak, atama işlemi değer değiştirmez, değer olarak belirtilen debug veya version ismini de etkinleştirir.

import std.stdio;

debug(hepsi) {
    debug = ikili_arama;
    debug = yigin_yapisi;
    version = denemeSürümü;
    version = okulSürümü;
}

void main() {
    debug(ikili_arama) writeln("ikili_arama etkin");
    debug(yigin_yapisi) writeln("yigin_yapisi etkin");

    version(denemeSürümü) writeln("deneme sürümü");
    version(okulSürümü) writeln("okul sürümü");
}

Yukarıdaki koddaki debug(hepsi) bloğu içindeki atamalar o isimlerin de etkinleşmelerini sağlar. Böylece bu program için derleme satırında dört debug ve version seçeneği ayrı ayrı seçilebileceği gibi, -debug=hepsi kullanıldığında; ikili_arama, yigin_yapisi, denemeSürümü, ve okulSürümü sanki komut satırında bildirilmişler gibi etkinleşirler:

$ dmd deneme.d -ofdeneme -w -debug=hepsi
$ ./deneme 
ikili_arama etkin
yigin_yapisi etkin
deneme sürümü
okul sürümü
static if

Programın çalışması sırasındaki kararlarda çok kullandığımız if koşulunun derleme zamanındaki eşdeğeri static if'tir.

if koşulunda olduğu gibi, static if koşulu da bir mantıksal ifade ile kullanılır. static if bloğundaki kodlar bu mantıksal ifade true olduğunda derlenir ve programa dahil edilir, false olduğunda ise o kodlar sanki hiç yazılmamışlar gibi etkisizleşirler. Yine if'e benzer şekilde, else static if ve else blokları da bulunabilir.

Derleme zamanında işletildiğinden, mantıksal ifadenin sonucunun derleme zamanında biliniyor olması şarttır.

static if her kapsamda kullanılabilir: Modül dosyasında en üst düzeyde veya yapı, sınıf, şablon, işlev, vs. kapsamlarında. Koşul sağlandığında blok içindeki kodlar yazıldıkları satırlarda programa dahil edilirler.

static if şablon tanımlarında, is ifadesi ile birlikte, ve __traits olanağı ile çok kullanılır.

static if'in is ifadesi ile birlikte kullanım örneklerini bir sonraki bölümde göreceğiz. Burada çok basit bir şablon tanımında kullanalım:

import std.stdio;

struct VeriYapısı(T) {
    static if (is (T == float)) {
        alias SonuçTürü = double;

    } else static if (is (T == double)) {
        alias SonuçTürü = real;

    } else {
        static assert(false, T.stringof ~ " desteklenmiyor");
    }

    SonuçTürü işlem() {
        writefln("%s için sonuç türü olarak %s kullanıyorum.",
                 T.stringof, SonuçTürü.stringof);
        SonuçTürü sonuç;
        // ...
        return sonuç;
    }
}

void main() {
    auto f = VeriYapısı!float();
    f.işlem();

    auto d = VeriYapısı!double();
    d.işlem();
}

VeriYapısı yalnızca float ve double türleriyle kullanılabilen bir tür. İşlem sonucunu hep bir adım daha hassas olan türde gerçekleştirmek için float ile kullanıldığında double, double ile kullanıldığında ise real seçiyor:

float için sonuç türü olarak double kullanıyorum.
double için sonuç türü olarak real kullanıyorum.

static if zincirleri oluştururken else static if yazmak gerektiğine dikkat edin. Yanlışlıkla else if yazıldığında, static if'in else bloğu olarak if kullanılacak demektir ve if de doğal olarak çalışma zamanında işletilecektir.

static assert

Aslında bir koşullu derleme olanağı olarak kabul edilmese de bu olanağı static if'e benzerliği nedeniyle burada tanıtmaya karar verdim.

Çalışma zamanında kullanmaya alıştığımız assert'le aynı biçimde ama derleme zamanında işletilir. Mantıksal ifadesi false olduğunda derlemenin bir hata ile sonlandırılmasını sağlar.

static if gibi static assert de programda herhangi bir kapsamda bulunabilir.

static assert kullanımının bir örneğini yukarıdaki programda gördük: float veya double türlerinden başka bir tür belirtildiğinde derleme static assert(false) nedeniyle sonlanır:

    auto i = VeriYapısı!int();

Derleme hatası:

Error: static assert  "int desteklenmiyor"

Başka bir örnek olarak belirli bir algoritmanın yalnızca belirli büyüklükteki türlerle doğru olarak çalışabildiğini varsayalım. Bu koşulu bir static assert ile denetleyebiliriz:

T birAlgoritma(T)(T değer) {
    // Bu algoritma ancak büyüklüğü dördün katı olan türlerle
    // çalışabilir
    static assert((T.sizeof % 4) == 0);

    // ...
}

O işlev şablonu örneğin char ile çağrıldığında programın derlenmesi bir hata ile sonlanır:

Error: static assert  (1LU == 0LU) is false

Böylece algoritmanın uygunsuz bir türle kullanılmasının ve olasılıkla hatalı çalışmasının önüne geçilmektedir.

static assert de is ifadesi dahil olmak üzere derleme zamanında oluşturulabilen her mantıksal ifade ile kullanılabilir.

Tür nitelikleri

__traits anahtar sözcüğü ve std.traits modülü türlerin nitelikleriyle ilgili bilgileri derleme zamanında edinmeye yarar.

__traits, derleyicinin koddan edinmiş olduğu bilgileri sorgulamaya yarar. Söz dizimi aşağıdaki gibidir:

    __traits(sözcük, parametreler)

sözcük, __traits'in hangi amaçla kullanıldığını belirtir. parametreler ise bir veya daha fazla sayıda olmak üzere tür ismi veya ifadedir. Parametrelerin anlamları kullanılan sözcüğe bağlıdır.

__traits'in sunduğu bilgiler dilin başka olanakları tarafından edinilemeyen ve çoğunlukla derleyicinin toplamış olduğu bilgilerdir. Bu bilgiler özellikle şablon kodlarında ve koşullu derleme sırasında yararlıdır.

Örneğin, "aritmetik mi" anlamına gelen isArithmetic, T gibi bir şablon parametresinin aritmetik bir tür olup olmamasına göre farklı kod üretmek için kullanılabilir:

    static if (__traits(isArithmetic, T)) {
        // ... aritmetik bir türmüş ...

    } else {
        // ... değilmiş ...
    }

std.traits modülü de tür nitelikleriyle ilgili bilgileri şablon olanakları olarak yine derleme zamanında sunar. Örneğin, std.traits.isSomeChar, kendisine verilen şablon parametresi bir karakter türü olduğunda true üretir:

import std.traits;

// ...

    static if (isSomeChar!T) {
        // ... herhangi bir karakter türüymüş ...

    } else {
        // ... bir karakter türü değilmiş ...
    }

Daha fazla bilgi için __traits belgesine ve std.traits belgesine başvurabilirsiniz.

Özet