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( int başlangıçSaati, int başlangıçDakikası, int eklenecekSaat, 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(GününSaati başlangıç, 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(GününSaati başlangıç,
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(GününSaati başlangıç,
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
- 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 birdchar
ü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');
- Bir
OyunKağıdı
nesnesi alan ve o nesneyi çıkışa yazdıranoyunKağıdıYazdır
isminde bir işlev tanımlayın:struct OyunKağıdı { // ... burasını siz yazın ... } void oyunKağıdıYazdır(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.
-
İsmi
yeniDeste
olan bir işlev yazın. Elli iki farklı oyun kağıdını temsil edenOyunKağıdı
[] türünde bir dilim döndürsün:OyunKağıdı[] yeniDeste() out (sonuç) { assert(sonuç.length == 52); } do { // ... 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
- Desteyi karıştıran bir işlev yazın.
std.random
modülünde tanımlı olanuniform
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, 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.