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

dönüş değeri: [return value], işlevin üreterek döndürdüğü değer
ilklemek: [initialize], ilk değerini vermek
isimli ilklendirici: [designated initializer], yapı üyelerinin isimle ilklendirilmeleri
iş parçacığı: [thread], işletim sisteminin program işletme birimi
kapsam: [scope], küme parantezleriyle belirlenen bir alan
kurma: [construct], yapı veya sınıf nesnesini kullanılabilir duruma getirmek
nesne: [object], belirli bir sınıf veya yapı türünden olan değişken
referans: [reference], asıl nesneye, onun takma ismi gibi erişim sağlayan program yapısı
sabit: [const], bir bağlamda değiştirilmeyen
üye: [member], yapı veya sınıfın özel değişkenleri ve nesneleri
varsayılan: [default], özellikle belirtilmediğinde kullanılan
yan etki: [side effect], bir ifadenin, ürettiği değer dışındaki etkisi
yapı: [struct], başka verileri bir araya getiren veri yapısı
... bütün sözlük



İngilizce Kaynaklar


Diğer




Yapılar

Kitabın başında temel türlerin üst düzey kavramları ifade etmede yetersiz kalacaklarını söylemiştim. int türünden bir tamsayı örneğin iki olay arasında geçen süreyi dakika türünden ifade etmek için kullanılabilir; ama böyle bir değer her zaman tek başına kullanışlı olamaz. Değişkenler bazen başka değişkenlerle bir araya geldiklerinde anlam kazanırlar.

Yapılar temel türleri, başka yapıları, veya sınıfları bir araya getirerek yeni türler oluşturmaya yarayan olanaklardır. Yeni tür struct anahtar sözcüğü ile oluşturulur. Bu tanıma göre, yapılar kullanıcı türleridir. struct, "yapı" anlamına gelen "structure"ın kısaltmasıdır.

Bu bölümde yapılarla ilgili olarak anlatacağım çoğu bilgi, daha sonra göreceğimiz sınıfları anlamada da yardımcı olacak. Özellikle bir araya getirerek yeni tür tanımlama kavramı, yapılarda ve sınıflarda aynıdır.

Yapı kavramını anlamak için daha önce assert ve enforce bölümünde gördüğümüz zamanEkle işlevine bakalım. Aşağıdaki tanım o bölümün problem çözümlerinde geçiyordu:

void zamanEkle(
        in int başlangıçSaati, in int başlangıçDakikası,
        in int eklenecekSaat, in int eklenecekDakika,
        out int sonuçSaati, out int sonuçDakikası) {
    sonuçDakikası = başlangıçDakikası + eklenecekDakika;
    sonuçSaati = başlangıçSaati + eklenecekSaat;
    sonuçSaati += sonuçDakikası / 60;

    sonuçDakikası %= 60;
    sonuçSaati %= 24;
}

Not: İşlevin in, out, ve unittest bloklarını fazla yer tutmamak için bu bölümde göstermiyorum.

Her ne kadar o işlev altı tane parametre alıyor gibi görünse de, birbirleriyle ilgili olan parametreleri çifter çifter düşünürsek, aslında üç çift bilgi aldığını görürüz. O çiftlerden ilk ikisi giriş olarak, sonuncusu da çıkış olarak kullanılmaktadır.

Tanımlanması

struct birbirleriyle ilişkili değişkenleri bir araya getirerek yeni bir tür olarak kullanma olanağı verir:

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

Yukarıdaki tanım, saat ve dakika isimli iki int değişkeni bir araya getiren ve ismi GününSaati olan yeni bir tür tanımlar. Yukarıdaki tanımdan sonra artık başka türler gibi kullanabileceğimiz GününSaati isminde yeni bir türümüz olur. Örnek olarak int türüne benzer kullanımını şöyle gösterebiliriz:

    int sayı;              // bir değişken
    sayı = başkaSayı;      // başkaSayı'nın değerini alması

    GününSaati zaman;      // bir nesne
    zaman = başkaZaman;    // başkaZaman'ın değerini alması

Yapı türleri şöyle tanımlanır:

struct Türİsmi {
    // ... türü oluşturan üyeler ve varsa özel işlevleri ...
}

Yapılar için özel işlevler de tanımlanabilir. Bunları daha sonraki bir bölümde anlatacağım. Bu bölümde yalnızca yapı üyelerini gösteriyorum.

Yapının bir araya getirdiği parçalara üye adı verilir. Bu tanıma göre, yukarıdaki GününSaati yapısının iki üyesi vardır: saat ve dakika.

struct tür tanımıdır, değişken tanımı değildir

Burada bir uyarıda bulunmam gerekiyor: İsim Alanı bölümünde ve Yaşam Süreçleri bölümünde anlatılanlar doğrultusunda; yapı tanımında kullanılan küme parantezlerine bakarak, o kapsam içindeki üyelerin yapının tanımlandığı an yaşamaya başladıklarını düşünebilirsiniz. Bu doğru değildir.

Yapı tanımı, değişken tanımlamaz:

struct GününSaati {
    int saat;      // ← Değişken tanımı değildir; daha sonra
                   //   bir yapı nesnesinin parçası olacaktır.

    int dakika;    // ← Değişken tanımı değildir; daha sonra
                   //   bir yapı nesnesinin parçası olacaktır.
}

Yapı tanımı, daha sonradan yapı nesneleri oluşturulduğunda ne tür üye değişkenlerinin olacağını belirler. O üye değişkenler bu yapı türünden bir nesne oluşturulduğu zaman o nesnenin parçası olarak oluşturulurlar:

    GününSaati yatmaZamanı;   // içinde kendi saat ve dakika
                              // değişkenlerini barındırır

    GününSaati kalkmaZamanı;  // bu da kendi saat ve dakika
                              // değişkenlerini barındırır;
                              // bunun saat ve dakika
                              // değişkenleri öncekinden
                              // bağımsızdır

Yapı ve sınıf değişkenlerine nesne denir.

Kodlama kolaylığı

Saat ve dakika gibi iki bilgiyi böyle bir araya getirerek tek bir tür gibi kullanabilmek büyük kolaylık sağlar. Örneğin yukarıdaki işlev altı tane int yerine, asıl amacına çok daha uygun olacak şekilde üç tane GününSaati türünde parametre alabilir:

void zamanEkle(in GününSaati başlangıç,
               in GününSaati eklenecek,
               out GününSaati sonuç) {
    // ...
}

Not: Günün saatini belirten böyle iki değerin eklenmesi aslında normal bir işlem olarak kabul edilmemelidir. Örneğin kahvaltı zamanı olan 7:30'a öğle yemeği zamanı olan 12:00'yi eklemek doğal değildir. Burada aslında Süre diye yeni bir tür tanımlamak ve GününSaati nesnelerine Süre nesnelerini eklemek çok daha doğru olurdu. Ben bu bölümde yine de yalnızca GününSaati türünü kullanacağım.

Hatırlayacağınız gibi, işlevler return deyimiyle tek bir değer döndürebilirler. zamanEkle ürettiği saat ve dakika değerlerini zaten bu yüzden iki tane out parametre olarak tanımlamak zorunda kalıyordu. Ürettiği iki tane sonucu tek bir değer olarak döndüremiyordu.

Yapılar bu kısıtlamayı da ortadan kaldırırlar: Birden fazla bilgiyi bir araya getirerek tek bir tür oluşturdukları için, işlevlerin dönüş türü olarak kullanılabilirler. Artık işlevden tek bir GününSaati nesnesi döndürebiliriz:

GününSaati zamanEkle(in GününSaati başlangıç,
                     in GününSaati eklenecek) {
    // ...
}

Böylece zamanEkle artık yan etki oluşturan değil, değer üreten bir işlev haline de gelmiş olur. İşlevler bölümünden hatırlayacağınız gibi; işlevlerin yan etki oluşturmak yerine değer üretmeleri tercih edilir.

Yapılar da yapı üyesi olabilirler. Örneğin GününSaati yapısından iki üyesi bulunan başka bir yapı şöyle tasarlanabilir:

struct Toplantı {
    string     konu;
    size_t     katılımcıSayısı;
    GününSaati başlangıç;
    GününSaati bitiş;
}

Toplantı yapısı da başka bir yapının üyesi olabilir. Yemek diye bir yapı olduğunu da varsayarsak:

struct GünlükPlan {
    Toplantı projeToplantısı;
    Yemek    öğleYemeği;
    Toplantı bütçeToplantısı;
}
Üye erişimi

Yapı üyelerini de herhangi bir değişken gibi kullanabiliriz. Tek fark, üyelere erişmek için nesnenin isminden sonra önce erişim işleci olan nokta, ondan sonra da üyenin isminin yazılmasıdır:

    başlangıç.saat = 10;

O satır, başlangıç nesnesinin saat üyesine 10 değerini atar.

Yapılarla ilgili bu kadarlık bilgiyi kullanarak zamanEkle işlevini artık şu şekilde değiştirebiliriz:

GününSaati zamanEkle(in GününSaati başlangıç,
                     in GününSaati eklenecek) {
    GününSaati sonuç;

    sonuç.dakika = başlangıç.dakika + eklenecek.dakika;
    sonuç.saat = başlangıç.saat + eklenecek.saat;
    sonuç.saat += sonuç.dakika / 60;

    sonuç.dakika %= 60;
    sonuç.saat %= 24;

    return sonuç;
}

Bu işlevde kullanılan değişken isimlerinin artık çok daha kısa seçilebildiğine dikkat edin: nesnelere başlangıç, eklenecek, ve sonuç gibi kısa isimler verebiliyoruz. başlangıçSaati gibi bileşik isimler kullanmak yerine de nesnelerin üyelerine nokta ile başlangıç.saat şeklinde erişiyoruz.

O işlevi kullanan bir kod aşağıdaki şekilde yazılabilir. Bu program, 1 saat 15 dakika süren ve 8:30'da başlayan dersin bitiş zamanını 9:45 olarak hesaplar:

void main() {
    GününSaati dersBaşı;
    dersBaşı.saat = 8;
    dersBaşı.dakika = 30;

    GününSaati dersSüresi;
    dersSüresi.saat = 1;
    dersSüresi.dakika = 15;

    immutable dersSonu = zamanEkle(dersBaşı, dersSüresi);

    writefln("Ders sonu: %s:%s",
             dersSonu.saat, dersSonu.dakika);
}

Çıktısı:

Ders sonu: 9:45

Yukarıdaki main'i şimdiye kadar bildiklerimizi kullanarak yazdım. Biraz aşağıda bu işlemlerin çok daha kolay ve kısa olanlarını göstereceğim.

Kurma

Yukarıdaki main'in ilk üç satırı, dersBaşı nesnesinin kurulması ile ilgilidir; ondan sonraki üç satır da dersSüresi nesnesinin kurulmasıdır. O satırlarda önce nesne tanımlanmakta, sonra saat ve dakika üyelerinin değerleri atanmaktadır.

Herhangi bir değişkenin veya nesnenin tutarlı bir şekilde kullanılabilmesi için mutlaka kurulması gerekir. Bu çok önemli ve yaygın bir işlem olduğu için, yapı nesneleri için kısa bir kurma söz dizimi vardır:

    GününSaati dersBaşı = GününSaati(8, 30);
    GününSaati dersSüresi = GününSaati(1, 15);

Nesneler bu şekilde kurulurken belirtilen değerler, yapının üyelerine yapı içinde tanımlandıkları sıra ile atanırlar: yapı içinde saat önce tanımlandığı için 8 değeri dersBaşı.saat'e, 30 değeri de dersBaşı.dakika'ya atanır.

Tür Dönüşümleri bölümünde gördüğümüz gibi, kurma söz dizimi başka türlerle de kullanılabilir:

    auto u = ubyte(42);    // u'nun türü ubyte olur
    auto i = int(u);       // i'nin türü int olur
immutable olarak kurabilme olanağı

Nesneleri aynı anda hem tanımlamak hem de değerlerini verebilmek, onları immutable olarak işaretleme olanağı da sağlar:

    immutable dersBaşı = GününSaati(8, 30);
    immutable dersSüresi = GününSaati(1, 15);

Kurulduktan sonra artık hiç değişmeyecek oldukları durumlarda, bu nesnelerin sonraki satırlarda yanlışlıkla değiştirilmeleri böylece önlenmiş olur. Yukarıdaki programda ise nesneleri immutable olarak işaretleyemezdik, çünkü ondan sonra üyelerinin değerlerini atamamız mümkün olmazdı:

    immutable GününSaati dersBaşı;
    dersBaşı.saat = 8;      // ← derleme HATASI
    dersBaşı.dakika = 30;   // ← derleme HATASI

Doğal olarak, immutable olarak işaretlendiği için değişemeyen dersBaşı nesnesinin üyelerini değiştirmek olanaksızdır.

Sondaki üyelerin değerleri boş bırakılabilir

Yapı nesneleri kurulurken sondaki üyelerin değerleri belirtilmeyebilir. Bu durumda sondaki üyeler yine de otomatik olarak kendi türlerinin .init değeri ile ilklenirler.

Bunu gösteren aşağıdaki programda Deneme türü gittikçe azalan sayıda parametre ile kuruluyor ve geri kalan parametrelerin de otomatik olarak ilklendikleri assert denetimleri ile gösteriliyor (programda kullanmak zorunda kaldığım isNaN işlevini programdan sonra açıklıyorum):

import std.math;

struct Deneme {
    char   karakter;
    int    tamsayı;
    double kesirli;
}

void main() {
    // Bütün değerlerle
    auto d1 = Deneme('a', 1, 2.3);
    assert(d1.karakter == 'a');
    assert(d1.tamsayı == 1);
    assert(d1.kesirli == 2.3);

    // Sonuncusu eksik
    auto d2 = Deneme('a', 1);
    assert(d2.karakter == 'a');
    assert(d2.tamsayı == 1);
    assert(isNaN(d2.kesirli));

    // Son ikisi eksik
    auto d3 = Deneme('a');
    assert(d3.karakter == 'a');
    assert(d3.tamsayı == int.init);
    assert(isNaN(d3.kesirli));

    // Hiçbirisi yok
    auto d4 = Deneme();
    assert(d4.karakter == char.init);
    assert(d4.tamsayı == int.init);
    assert(isNaN(d4.kesirli));

    // Yukarıdakiyle aynı şey
    Deneme d5;
    assert(d5.karakter == char.init);
    assert(d5.tamsayı == int.init);
    assert(isNaN(d5.kesirli));
}

Kesirli Sayılar bölümünden hatırlayacağınız gibi double'ın ilk değeri double.nan'dır ve bir değerin .nan'a eşit olup olmadığı == işleci ile denetlenemez. O yüzden yukarıdaki programda std.math.isNaN'dan yararlanılmıştır.

Varsayılan üye değerlerinin belirlenmesi

Üyelerin otomatik olarak ilkleniyor olmaları çok yararlı bir olanaktır. Üyelerin rasgele değerlerle kullanılmaları önlenmiş olur. Ancak, her üyenin kendi türünün .init değerini alması her duruma uygun değildir. Örneğin char.init değeri geçerli bir karakter bile değildir.

Bu yüzden üyelerin otomatik olarak alacakları değerler programcı tarafından belirlenebilir. Bu sayede örneğin yukarıda gördüğümüz ve hiçbir kullanışlılığı olmayan double.nan değeri yerine, çoğu zaman çok daha uygun olan 0.0 değerini kullanabiliriz.

Üyelerin aldıkları bu özel ilk değerlere varsayılan değer denir ve üye tanımından sonraki atama söz dizimiyle belirlenir:

struct Deneme {
    char   karakter = 'A';
    int    tamsayı  = 11;
    double kesirli  = 0.25;
}

Üye tanımı sırasında kullanılan bu yazım şeklinin bir atama işlemi olmadığına dikkat edin. Yukarıdaki kodun tek amacı, üyeler için hangi değerlerin varsayılacağını belirlemektir. Bu değerler, daha sonra nesne oluşturulurken gerekirse kullanılacaktır.

Nesne kurulurken değerleri özellikle belirtilmeyen üyeler o varsayılan değerleri alırlar. Örneğin aşağıdaki kullanımda nesnenin hiçbir üyesinin değeri verilmemektedir:

    Deneme d;  // hiçbir üye değeri belirtilmiyor
    writefln("%s,%s,%s", d.karakter, d.tamsayı, d.kesirli);

Bütün üyeler türün tanımında belirtilmiş olan ilk değerlere sahip olurlar:

A,11,0.25
{} karakterleriyle kurma

Yukarıdaki kurma söz dizimi varken kullanmaya gerek olmasa da, bunu da bilmeniz gerekir. Yapı nesnelerini başka bir söz dizimiyle de kurabilirsiniz:

    GününSaati dersBaşı = { 8, 30 };

Belirlenen değerler bu kullanımda da üyelere sıra ile atanırlar; ve bu kullanımda da üye sayısından daha az değer verilebilir.

Bu söz dizimi D'ye C programlama dilinden geçmiştir:

    auto dersBaşı = GününSaati(8, 30);   // ← normal
    GününSaati dersSonu = { 9, 30 };     // ← C söz dizimi

Bu söz diziminin bir yararı, isimli ilklendirici olanağıdır. Verilen bir değerin hangi üye ile ilgili olduğu üyenin ismi ile belirtilebilir. Bu olanak, üyelerin yapı içindeki tanımlarından farklı sırada ilklenmelerine de izin verir:

    GününSaati g = { dakika: 42, saat: 7 };
Kopyalama ve Atama

Yapılar değer türleridir. Bundan; Değerler ve Referanslar bölümünde açıklandığı gibi, her yapı nesnesinin kendisine ait değeri olduğunu anlarız. Kurulduklarında kendi değerlerini edinirler; atandıklarında da yalnızca kendi değerleri değişir.

    auto seninYemekSaatin = GününSaati(12, 0);
    auto benimYemekSaatim = seninYemekSaatin;

    // Yalnızca benim yemek saatim 12:05 olur:
    benimYemekSaatim.dakika += 5;

    // ... senin yemek saatin değişmez:
    assert(seninYemekSaatin.dakika == 0);

Kopyalama sırasında bir nesnenin bütün üyeleri sırayla diğer üyeye kopyalanır. Benzer şekilde, atama işlemi de bütün üyelerin sırayla atanmaları anlamına gelir.

Bu konuda referans türünden olan üyelere özellikle dikkat etmek gerekir.

Referans türünden olan üyelere dikkat!

Burada çok önemli bir konuyu hatırlatmak gerekiyor: Referans türünden olan değişkenler kopyalandıklarında veya atandıklarında asıl nesne değişmez, ona erişim sağlayan referans değişir, ve sonuçta asıl nesneye birden fazla referans tarafından erişim sağlanmış olur.

Bunun yapı üyeleri açısından önemi, iki farklı yapı nesnesinin üyelerinin aynı asıl nesneye erişim sağlıyor olacaklarıdır. Bunu görmek için referans türünden bir üyesi olan bir yapıya bakalım. Bir öğrencinin numarasını ve notlarını içeren şöyle bir yapı tanımlanmış olsun:

struct Öğrenci {
    int numara;
    int[] notlar;
}

O türden bir nesnenin başka bir nesnenin değeriyle kurulduğu şu koda bakalım:

    // Birinci öğrenciyi kuralım:
    auto öğrenci1 = Öğrenci(1, [ 70, 90, 85 ]);

    // İkinci öğrenciyi birincinin kopyası olarak kuralım ...
    auto öğrenci2 = öğrenci1;

    // ... ve sonra numarasını değiştirelim:
    öğrenci2.numara = 2;

    // DİKKAT: İki öğrenci bu noktada aynı notları paylaşmaktadırlar!

    // İlk öğrencinin notunda bir değişiklik yaptığımızda ...
    öğrenci1.notlar[0] += 5;

    // ... ikinci öğrencinin notunun da değiştiğini görürüz:
    writeln(öğrenci2.notlar[0]);

öğrenci2 nesnesi kurulduğu zaman, üyeleri sırayla öğrenci1'in üyelerinin değerlerini alır. int bir değer türü olduğu için, her iki Öğrenci nesnesinin ayrı numara değeri olur.

Her iki Öğrenci nesnesinin birbirlerinden bağımsız olan notlar üyeleri de vardır. Ancak, dizi dilimleri referans türleri olduklarından, her ne kadar notlar üyeleri bağımsız olsalar da, aslında aynı asıl dizinin elemanlarına erişim sağlarlar. Sonuçta, bir nesnenin notlar üyesinde yapılan değişiklik diğerini de etkiler.

Yukarıdaki kodun çıktısı, iki öğrenci nesnesinin aynı asıl notları paylaştıklarını gösterir:

75

Belki de burada hiç kopyalama işlemini kullanmadan, ikinci nesneyi kendi numarasıyla ve birincinin notlarının kopyasıyla kurmak daha doğru olur:

    // İkinci öğrenciyi birincinin notlarının kopyası ile
    // kuruyoruz
    auto öğrenci2 = Öğrenci(2, öğrenci1.notlar.dup);

    // İlk öğrencinin notunda bir değişiklik yaptığımızda ...
    öğrenci1.notlar[0] += 5;

    // ... ikinci öğrencinin notu bu sefer değişmez:
    writeln(öğrenci2.notlar[0]);

Dizilerin .dup niteliği ile kopyalandığı için bu sefer her nesnenin ayrı notları olur. Şimdiki çıktı, ikinci öğrencinin notunun etkilenmediğini gösterir:

70

Not: İstenen durumlarda referans türünden üyelerin otomatik olarak kopyalanmaları da sağlanabilir. Bunu daha sonra yapı işlevlerini anlatırken göstereceğim.

Yapı hazır değerleri

Nasıl 10 gibi hazır değerleri hiç değişken tanımlamak zorunda kalmadan işlemlerde kullanabiliyorsak, yapı nesnelerini de isimleri olmayan hazır değerler olarak kullanabiliriz.

Yapı hazır değerlerini oluşturmak için yine kurma söz dizimi kullanılır ve yapı nesnesi gereken her yerde kullanılabilir.

    GününSaati(8, 30) // ← hazır değer

Yukarıdaki main işlevini şimdiye kadar öğrendiklerimizi kullanarak şöyle yazabiliriz:

void main() {
    immutable dersBaşı = GününSaati(8, 30);
    immutable dersSüresi = GününSaati(1, 15);

    immutable dersSonu = zamanEkle(dersBaşı, dersSüresi);

    writefln("Ders sonu: %s:%s",
             dersSonu.saat, dersSonu.dakika);
}

Dikkat ederseniz, o programda dersBaşı ve dersSüresi nesnelerinin açıkça belirtilmelerine gerek yoktur. Onlar yalnızca dersSonu nesnesini hesaplamak için kullanılan aslında geçici nesnelerdir. O nesneleri açıkça tanımlamak yerine, zamanEkle işlevine şu şekilde hazır değer olarak da gönderebiliriz:

void main() {
    immutable dersSonu = zamanEkle(GününSaati(8, 30),
                                   GününSaati(1, 15));

    writefln("Ders sonu: %s:%s",
             dersSonu.saat, dersSonu.dakika);
}
static üyeler

Çoğu durumda her yapı nesnesinin kendi üyelerine sahip olmasını isteriz. Bazı durumlarda ise belirli bir yapı türünden olan bütün nesnelerin tek bir değişkeni paylaşmaları uygun olabilir. Bu, o yapı türü için akılda tutulması gereken genel bir bilgi bulunduğunda gerekebilir.

Bütün nesnelerin tek bir üyeyi paylaşmalarının bir örneği olarak, her bir nesne için farklı bir tanıtıcı numara olduğu bir durum düşünelim:

struct Nokta {
    // Her nesnenin kendi tanıtıcı numarası
    size_t numara;

    int satır;
    int sütun;
}

Her nesneye farklı bir numara verebilmek için sonrakiNumara gibi bir değişken barındırmak, ve her nesne için o sayıyı bir arttırmak gerekir:

Nokta NoktaOluştur(int satır, int sütun) {
    size_t numara = sonrakiNumara;
    ++sonrakiNumara;

    return Nokta(numara, satır, sütun);
}

Burada karar verilmesi gereken şey, her nesnenin oluşturulması sırasında ortak olarak kullanılan sonrakiNumara bilgisinin nerede tanımlanacağıdır. static üyeler işte bu gibi durumlarda kullanışlıdırlar.

O bilgi bir yapı üyesi olarak tanımlanır ve static olarak işaretlenir. Diğer üyelerin aksine, böyle üyelerden her iş parçacığında yalnızca bir adet oluşturulur. (Çoğu program yalnızca main()'in işlediği tek iş parçacığından oluşur.):

import std.stdio;

struct Nokta {
    // Her nesnenin kendi tanıtıcı numarası
    size_t numara;

    int satır;
    int sütun;

    // Bundan sonra oluşturulacak olan nesnenin numarası
    static size_t sonrakiNumara;
}

Nokta NoktaOluştur(int satır, int sütun) {
    size_t numara = Nokta.sonrakiNumara;
    ++Nokta.sonrakiNumara;

    return Nokta(numara, satır, sütun);
}

void main() {
    auto üstteki = NoktaOluştur(7, 0);
    auto ortadaki = NoktaOluştur(8, 0);
    auto alttaki =  NoktaOluştur(9, 0);

    writeln(üstteki.numara);
    writeln(ortadaki.numara);
    writeln(alttaki.numara);
}

sonrakiNumara her seferinde bir arttırıldığı için her nesnenin farklı numarası olur:

0
1
2

static üyeler bütün türe ait olduklarından onlara erişmek için bir nesne olması gerekmez. O üyeye türün ismi kullanılarak erişilebileceği gibi, o türün bir nesnesi üzerinden de erişilebilir:

    ++Nokta.sonrakiNumara;
    ++alttaki.sonrakiNumara;     // üst satırın eşdeğeri

İş parçacığı başına tek değişken yerine bütün programda tek değişken gerektiğinde o değişkenin shared static olarak tanımlanması gerekir. shared anahtar sözcüğünü daha sonraki bir bölümde göreceğiz.

İlk işlemler için static this(), son işlemler için static ~this()

Yukarıda sonrakiNumara üyesini özel bir değerle ilklemedik ve otomatik ilk değeri olan sıfırdan yararlandık. Gerektiğinde özel bir değerle de ilkleyebilirdik:

    static size_t sonrakiNumara = 1000;

O yöntem ancak ilk değer derleme zamanında bilindiğinde kullanılabilir. Ek olarak, bazı durumlarda yapının kullanımına geçmeden önce bazı ilkleme kodlarının işletilmesi de gerekebilir. Bu gibi ilkleme kodları yapının static this() kapsamına yazılırlar.

Örneğin, aşağıdaki kod nesne numaralarını hep sıfırdan başlatmak yerine eğer mevcutsa özel bir ayar dosyasından okuyor:

import std.file;

struct Nokta {
// ...

    enum sonNumaraDosyası = "Nokta_son_numara_dosyasi";

    static this() {
        if (exists(sonNumaraDosyası)) {
            auto dosya = File(sonNumaraDosyası, "r");
            dosya.readf(" %s", &sonrakiNumara);
        }
    }
}

Bir yapının özel static this() kapsamındaki kodlar her iş parçacığında ayrı ayrı işletilir. Bu kodlar o yapı o iş parçacığında kullanılmaya başlanmadan önce otomatik olarak işletilir. İş parçacıklarının sayısından bağımsız olarak bütün programda tek kere işletilmesi gereken kodlar ise (örneğin, immutable değişkenlerin ilklenmeleri) shared static this() işlevlerinde tanımlanmalıdırlar. Bunları daha sonraki Veri Paylaşarak Eş Zamanlı Programlama bölümünde göreceğiz.

Benzer biçimde, static ~this() yapı türünün belirli bir iş parçacığındaki son işlemleri için, shared static ~this() de bütün programdaki son işlemleri içindir.

Örneğin, aşağıdaki static ~this() yukarıdaki static this() tarafından okunabilsin diye son numarayı ayar dosyasına kaydetmektedir:

struct Nokta {
// ...

    static ~this() {
        auto dosya = File(sonNumaraDosyası, "w");
        dosya.writeln(sonrakiNumara);
    }
}

Böylece, program nesne numaralarını artık hep kaldığı yerden başlatacaktır. Örneğin, ikinci kere çalıştırıldığında programın çıktısı aşağıdaki gibidir:

3
4
5
Problemler
  1. Tek bir oyun kağıdını temsil eden ve ismi OyunKağıdı olan bir yapı tasarlayın. Bu yapının kağıt rengi ve kağıt değeri için iki üyesi olduğu düşünülebilir.

    Renk için bir enum değer kullanabileceğiniz gibi; doğrudan ♠, ♡, ♢, ve ♣ karakterlerini de kullanabilirsiniz.

    Kağıt değeri olarak da bir int veya bir dchar üye kullanabilirsiniz. int seçerseniz 1'den 13'e kadar değerlerden belki de 1, 11, 12, ve 13 değerlerini sırasıyla as, vale, kız ve papaz için düşünebilirsiniz.

    Daha başka çözümler de bulunabilir. Örneğin kağıt değerini de bir enum olarak tanımlayabilirsiniz.

    Bu yapının nesnelerinin nasıl kurulacakları, üyeler için seçtiğiniz türlere bağlı olacak. Örneğin eğer her iki üyeyi de dchar türünde tasarladıysanız, şöyle kurulabilirler:

        auto kağıt = OyunKağıdı('♣', '2');
    
  2. Bir OyunKağıdı nesnesi alan ve o nesneyi çıkışa yazdıran oyunKağıdıYazdır isminde bir işlev tanımlayın:
    struct OyunKağıdı {
        // ... burasını siz yazın ...
    }
    
    void oyunKağıdıYazdır(in OyunKağıdı kağıt) {
        // ... burasını siz yazın ...
    }
    
    void main() {
        auto kağıt = OyunKağıdı(/* ... */);
        oyunKağıdıYazdır(kağıt);
    }
    

    Örneğin sinek ikiliyi çıktıya şu şekilde yazdırsın:

    ♣2
    

    Kupa asını da şu şekilde:

    ♡A
    

    O işlevin içeriği, doğal olarak yapıyı nasıl tasarladığınıza bağlı olacaktır.

  3. İsmi yeniDeste olan bir işlev yazın. Elli iki farklı oyun kağıdını temsil eden OyunKağıdı[] türünde bir dilim döndürsün:
    OyunKağıdı[] yeniDeste()
    out (sonuç) {
        assert(sonuç.length == 52);
    
    } body {
        // ... burasını siz yazın ...
    }
    

    Bu işlev örneğin şöyle kullanılabilsin:

        OyunKağıdı[] deste = yeniDeste();
    
        foreach (kağıt; deste) {
            oyunKağıdıYazdır(kağıt);
            write(' ');
        }
    
        writeln();
    

    Eğer destedeki her kağıt gerçekten farklı olmuşsa, şuna benzer bir çıktı olmalıdır:

    ♠2 ♠3 ♠4 ♠5 ♠6 ♠7 ♠8 ♠9 ♠0 ♠J ♠Q ♠K ♠A ♡2 ♡3 ♡4
    ♡5 ♡6 ♡7 ♡8 ♡9 ♡0 ♡J ♡Q ♡K ♡A ♢2 ♢3 ♢4 ♢5 ♢6 ♢7
    ♢8 ♢9 ♢0 ♢J ♢Q ♢K ♢A ♣2 ♣3 ♣4 ♣5 ♣6 ♣7 ♣8 ♣9 ♣0
    ♣J ♣Q ♣K ♣A
    
  4. Desteyi karıştıran bir işlev yazın. std.random modülünde tanımlı olan uniform işlevini kullanarak rasgele seçtiği iki kağıdın yerini değiştirsin. Bu işlemi kaç kere tekrarlayacağını da parametre olarak alsın:
    void karıştır(OyunKağıdı[] deste, in int değişTokuşAdedi) {
        // ... burasını siz yazın
    }
    

    Şu şekilde çağrılabilsin:

        OyunKağıdı[] deste = yeniDeste();
        karıştır(deste, 1);
    
        foreach (kağıt; deste) {
            oyunKağıdıYazdır(kağıt);
            write(' ');
        }
    
        writeln();
    

    değişTokuşAdedi ile belirtilen değer kadar değiş tokuş işlemi gerçekleştirsin. Örneğin 1 ile çağrıldığında şuna benzer bir çıktı versin:

    ♠2 ♠3 ♠4 ♠5 ♠6 ♠7 ♠8 ♠9 ♠0 ♠J ♠Q ♠K ♠A ♡2 ♡3 ♡4
    ♡5 ♡6 ♡7 ♡8 ♣4 ♡0 ♡J ♡Q ♡K ♡A ♢2 ♢3 ♢4 ♢5 ♢6 ♢7
    ♢8 ♢9 ♢0 ♢J ♢Q ♢K ♢A ♣2 ♣3 ♡9 ♣5 ♣6 ♣7 ♣8 ♣9 ♣0
    ♣J ♣Q ♣K ♣A
    

    değişTokuşAdedi olarak daha yüksek bir değer verdiğinizde deste iyice karışmış olmalıdır:

        karıştır(deste, 100);
    
    ♠4 ♣7 ♢9 ♢6 ♡2 ♠6 ♣6 ♢A ♣5 ♢8 ♢3 ♡Q ♢J ♣K ♣8 ♣4
    ♡J ♣Q ♠Q ♠9 ♢0 ♡A ♠A ♡9 ♠7 ♡3 ♢K ♢2 ♡0 ♠J ♢7 ♡7
    ♠8 ♡4 ♣J ♢4 ♣0 ♡6 ♢5 ♡5 ♡K ♠3 ♢Q ♠2 ♠5 ♣2 ♡8 ♣A
    ♠K ♣9 ♠0 ♣3
    

    Not: Deste karıştırmak için daha etkin bir yöntemi çözüm programında açıklıyorum.