İşleç Yükleme
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 üyelerini gösteriyorum:
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 tanımlanınca, 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 bilgiler doğrultusunda benzer işlemleri yapılar için ancak üye işlevlerle gerçekleştirebiliyoruz:
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ürlerde olduğu 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 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 opOpAssign(string işleç) ismiyle tanımlamak ve bu tanımın "+" işlemi için yapılmakta olduğunu belirtmek gerekir.
Şimdiye kadar gördüğümüz işlev tanımlarından farklı olan bu tanım, opOpAssign aslında bir işlev şablonu olduğundandır. Şablonları daha ilerideki derslerde göstereceğim; şimdilik işleç yükleme konusunda bu söz dizimini bir kalıp olarak uygulamak gerektiğini kabul etmenizi rica ediyorum:
struct GününSaati { // ... void opOpAssign(string işleç)(in Süre süre) // (1) if (işleç == "+") // (2) { dakika += süre.dakika; saat += dakika / 60; dakika %= 60; saat %= 24; } }
Yukarıdaki şablon tanımı iki parçadan oluşur:
üyeİşlevİsmi(string işleç): YukarıdakiopOpAssign'da olduğu gibi üyeİşlevİsmi'nin ve(string işleç)'in aynen yazılmaları gerekir; kullanılabilecek bütün üye işlev isimlerini aşağıda göreceğiz.if (işleç == "işleç_karakteri"): Aynı üye işlev ismi birden fazla işlev için kullanıldığından hangi işleç karakterinin tanımlanmakta olduğu bu söz dizimiyle belirtilir. Aslında bir şablon kısıtlaması olan bu söz diziminin ayrıntılarını da daha sonraki bir derste göreceğiz.
Derleyici, GününSaati nesnelerinin += işleciyle kullanıldığı yerlerde perde arkasında opOpAssign!"+" işlevini çağırır:
yemekZamanı += Süre(10);
// Üsttekinin eşdeğeri:
yemekZamanı.opOpAssign!"+"(Süre(10));
opOpAssign'dan sonra yazılan !"+", opOpAssign'ın + işleci için tanımı olan işlevin çağrılmakta olduğu anlamına gelir. Şablonlarla ilgili olan bu söz dizimini de daha ilerideki bir derste göstereceğim.
Kod içindeki += işlecine karşılık gelen üye işlevde "+=" değil, "+" kullanıldığına dikkat edin. opOpAssign'ın isminde geçen ve "değer ata" anlamına gelen "assign", zaten atama kavramını içerir; yani opOpAssign!"+", atamalı toplama işlemi olan += işlecinin tanımıdır.
Alışılmış olan davranışları değiştirmeyin
İş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şlevin içeriğini 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 herkesi yanıltacağını ve programda hatalara neden olacağını aklınızda bulundurun.
Tekli işleç
Tek nesneyle işleyen 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 de sağındakinin değerini solundakine atar.
Bu derste yüklenebilen bütün işleçlerin örneklerini 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
Bu işleçler opUnary üye işlev ismiyle tanımlanırlar; parametre almazlar çünkü yalnızca işlecin kullanıldığı nesneyi etkilerler.
İşlev tanımında kullanılması gereken işleç dizgileri şunlardır:
| İşleç | Anlamı | İşleç Dizgisi |
|---|---|---|
| -nesne | ters işaretlisini üret | "-" |
| +nesne | aynı işaretlisini üret | "+" |
| ~nesne | bit düzeyinde tersini al | "~" |
| *nesne | gösterdiğine eriş | "*" |
| ++nesne | bir arttır | "++" |
| --nesne | bir azalt | "--" |
Örneğin ++ işlecini Süre için şöyle tanımlayabiliriz:
struct Süre { int dakika; void opUnary(string işleç)() if (işleç == "++") { ++dakika; } }
Süre nesneleri bu sayede artık ++ işleci ile arttırılabilirler:
auto süre = Süre(20);
++süre;
Önceki değerli arttırma ve önceki değerli azaltma işleçleri olan nesne++ ve nesne-- kullanımları yüklenemez. O kullanımlardaki önceki değerleri derleyici otomatik olarak halleder. Örneğin nesne++ kullanımın eşdeğeri şudur:
/* Önceki değer derleyici tarafından otomatik olarak * kopyalanır: */ Süre __öncekiDeğer__ = süre; /* Tanımlanmış olan normal ++ işleci çağrılır: */ ++süre; /* Daha sonra bütün ifadede __öncekiDeğer__ kullanılır. */
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:
- a: aritmetik işleç
- b: bit düzeyinde işlem işleci
- m: mantıksal işleç;
booldöndürür - s: sıralama işleci; eksi değer, sıfır veya artı değer döndürür
- =: atamalı işleç; sol tarafı değiştirir
İşleç | Anlamı | Değişmeli (Komütatif) | İşlev İsmi | Sağ Taraf için İşlev İsmi | Tür |
|---|---|---|---|---|---|
| + | topla | evet | opBinary | opBinaryRight | a |
| - | çıkar | hayır | opBinary | opBinaryRight | a |
| * | çarp | evet | opBinary | opBinaryRight | a |
| / | böl | hayır | opBinary | opBinaryRight | a |
| % | kalanını hesapla | hayır | opBinary | opBinaryRight | a |
| ^^ | üssünü al | hayır | opBinary | opBinaryRight | a |
| & | bit işlemi ve | evet | opBinary | opBinaryRight | b |
| | | bit işlemi veya | evet | opBinary | opBinaryRight | b |
| ^ | bit işlemi ya da | evet | opBinary | opBinaryRight | b |
| << | sola kaydır | hayır | opBinary | opBinaryRight | b |
| >> | sağa kaydır | hayır | opBinary | opBinaryRight | b |
| >>> | işaretsiz sağa kaydır | hayır | opBinary | opBinaryRight | b |
| ~ | birleştir | hayır | opBinary | opBinaryRight | |
| == | eşittir | evet | opEquals | - | m |
| != | eşit değildir | evet | opEquals | - | m |
| < | öncedir | hayır | opCmp | - | s |
| <= | sonra değildir | hayır | opCmp | - | s |
| > | sonradır | hayır | opCmp | - | s |
| >= | önce değildir | hayır | opCmp | - | s |
| = | ata | hayır | opAssign | - | = |
| += | arttır | hayır | opOpAssign | - | = |
| -= | azalt | hayır | opOpAssign | - | = |
| *= | katını ata | hayır | opOpAssign | - | = |
| /= | bölümünü ata | hayır | opOpAssign | - | = |
| %= | kalanını ata | hayır | opOpAssign | - | = |
| ^^= | üssünü ata | hayır | opOpAssign | - | = |
| &= | & sonucunu ata | hayır | opOpAssign | - | = |
| |= | | sonucunu ata | hayır | opOpAssign | - | = |
| ^= | ^ sonucunu ata | hayır | opOpAssign | - | = |
| <<= | << sonucunu ata | hayır | opOpAssign | - | = |
| >>= | >> sonucunu ata | hayır | opOpAssign | - | = |
| >>>= | >>> sonucunu ata | hayır | opOpAssign | - | = |
| ~= | sonuna ekle | hayır | opOpAssign | - | = |
| in | içinde mi? | hayır | opBinary | opBinaryRight | 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.opBinary!"işleç"(y); y.opBinaryRight!"işleç"(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.opBinaryRight!"işleç"(y); y.opBinary!"işleç"(x);
Normalde bu kadar karmaşık kuralları bilmek zorunda kalmayız. Çoğu durumda bu işlevlerden ismi Right ile sonlananların hiç tanımlanmasına gerek yoktur. (Bu durum in işlecinde tam tersidir: Onun için çoğunlukla opBinaryRight tanımlanır.)
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 opBinary işlecini "+" işleci için şöyle yükleyebiliriz:
struct Süre { int dakika; Süre opBinary(string işleç)(in Süre sağdaki) const if (işleç == "+") { 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 o 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.opBinary!"+"(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. (Not: Bunun karışıklık doğuracağı açıktır; örnek olsun diye öylesine tanımladım.)
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:
- soldaki nesne önce olduğunda eksi bir değer
- sağdaki nesne önce olduğunda artı bir değer
- ikisi eşit olduklarında sıfır değeri
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 bir değer, sağdaki nesne önce olduğunda artı bir değer döndürür.
opCmp'un tanımlanmış olması, bu türün sıralama algoritmalarıyla da kullanılabilmesini sağlar. Sıralama algoritmalarının bir örneğini dizilerin .sort niteliğinde görmüştük. Aşağıdaki program 10 adet rasgele zaman değeri oluşturur ve onları dizinin .sort niteliği ile sıralar. Sıralamayı belirlerken nesneler karşılaştırılırken hep opCmp işletilir:
import std.random; import std.stdio; 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; writeln(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. Bunun sonucunda denklem nesnesi, y = 1.2x + 3.4 denklemini ifade etmeye başlar. 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 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) {
writefln("%f: %f", x, denklem(x));
}
O da y = 0.01x + 0.4 denklemini x'in 0.0 ile 1.0 aralığındaki 0.125'in katı olan değerleri için hesaplar.
Dizi erişim işleçleri opIndex, opIndexAssign, opIndexUnary ve opIndexOpAssign
Bu işleçler, nesneyi nesne[indeks] şeklinde dizi gibi kullanma olanağı verirler.
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]; // erişim birNot = bütünNotlar.opIndex(3, 1); // üsttekinin eşdeğeri
opIndexUnary, opUnary'nin eşdeğeridir; farkı, işlemin belirtilen indeksteki eleman üzerinde işleyecek olmasıdır:
++bütünNotlar[4, 0]; // arttırma bütünNotlar.opIndexUnary!"++"(4, 0);// üsttekinin eşdeğeri
opIndexAssign 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; // atama bütünNotlar.opIndexAssign(95, 1, 1);// üsttekinin eşdeğeri
opIndexOpAssign, opOpAssign'ın eşdeğeridir; farkı, atamalı işlemin belirtilen indeksteki eleman üzerinde işleyecek olmasıdır:
bütünNotlar[2, 1] += 42; // atamalı arttırma bütünNotlar.opIndexOpAssign!"+"(42, 2, 1); // üsttekinin eşdeğeri
Bütün bu işleçlerle kullanılan bir örnek olarak, bütün öğrencilerin bütün notlarını içeren bir yapıyı şöyle tanımlayabiliriz:
import std.stdio; struct BütünÖğrencilerinNotları { // 5 öğrenci için 2 not int[2][5] notlar; // Belirtilen notu döndürür int opIndex(int öğrenciİndeksi, int notİndeksi) { return notlar[öğrenciİndeksi][notİndeksi]; } // Belirtilen notu bir arttırır void opIndexUnary(string işleç)(int öğrenciİndeksi, int notİndeksi) if (işleç == "++") { ++notlar[öğrenciİndeksi][notİndeksi]; } // Belirtilen notu belirtilen öğrencinin notuna atar int opIndexAssign(int not, int öğrenciİndeksi, int notİndeksi) { return notlar[öğrenciİndeksi][notİndeksi] = not; } // Belirtilen öğrencinin notunu arttırır void opIndexOpAssign(string işleç)(int artış, int öğrenciİndeksi, int notİndeksi) if (işleç == "+") { notlar[öğrenciİndeksi][notİndeksi] += artış; } } void main() { BütünÖğrencilerinNotları bütünNotlar; int birNot = bütünNotlar[3, 1]; // erişim ++bütünNotlar[4, 0]; // arttırma bütünNotlar[1, 1] = 95; // atama bütünNotlar[2, 1] += 42; // atamalı arttırma writeln(bütünNotlar.notlar); }
Erişim satırını yalnızca göstermek amacıyla kullandım; programda bir amacı bulunmuyor. Diğer satırlar ise belirtilen öğrencilerin belirtilen notlarını etkilemiş oluyor:
[[0, 0], [0, 95], [0, 42], [0, 0], [1, 0]]
Dilim işleçleri opSlice, opSliceUnary, opSliceAssign, ve opSliceOpAssign
Yukarıdaki opIndex işleçlerine çok benzerler; farkları, nesneleri dilim işleciyle kullanma olanağı sağlamalarıdır. Bütün bu tanımların 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 sekiz kullanımın hangi işlevler tarafından yüklendiklerini bu işlevleri tanımlamadan gösteren bir örnek:
class BirTür { int opSlice(); // nesne[] kullanımı int opSlice(int i, int j); // nesne[i .. j] kullanımı void opSliceUnary(string işleç)() if (işleç == "++"); // ++nesne[] kullanımı void opSliceUnary(string işleç)(int i, int j) if (işleç == "++"); // ++nesne[i .. j] kullanımı int opSliceAssign(int değer); // nesne[] = değer kullanımı int opSliceAssign(int değer, int i, int j); // nesne[i .. j] = değer kullanımı void opSliceOpAssign(string işleç)(int değer) if (işleç == "+"); // nesne[] += değer kullanımı void opSliceOpAssign(string işleç)(int değer, int i, int j) if (işleç == "+"); // 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[]; nesne.opSliceUnary!"++"(); // üsttekinin eşdeğeri ++nesne[3 .. 4]; nesne.opSliceUnary!"++"(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 nesne[] += değer; nesne.opSliceOpAssign!"+"(değer); // üsttekinin eşdeğeri nesne[3 .. 4] += değer; nesne.opSliceOpAssign!"+"(değer, 3, 4); // üsttekinin eşdeğeri }
Tür dönüşümü işleci opCast
Elle açıkça yapılan tür dönüşümünü belirleyen opCast, dönüştürülecek her tür için ayrı ayrı yüklenebilir ve o tür opCast'in dönüş türü olarak yazılır. Bu işleç de şablon olarak tanımlanır ama kalıbı farklıdır: hangi dönüşümün tanımlanmakta olduğu (Tür : dönüştürülecek_tür) söz dizimiyle belirtilir:
dönüştürülecek_tür opCast(Tür : dönüştürülecek_tür)()
{
// ...
}
Yine şimdilik bir kalıp olarak kabul etmenizi isteyeceğim bu söz dizimini de daha sonra şablonlar dersinde anlatacağım.
Süre'nin saat ve dakikadan oluşan bir tür olduğunu kabul edelim. Bu türün nesnelerini double türüne dönüştüren işlev şöyle tanımlanabilir:
import std.stdio; struct Süre { int saat; int dakika; double opCast(Tür : double)() { return saat + (cast(double)dakika / 60); } } void main() { auto süre = Süre(2, 30); double kesirli = cast(double)süre; writeln(kesirli); }
Açıkça tür dönüşümü istenen satırda derleyici üye işlevi şöyle çağırır:
double kesirli = süre.opCast!double();
double türüne dönüştüren işleç, iki saat otuz dakikaya karşılık 2.5 değerini üretmektedir:
2.5
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.stdio; struct BirTür { void opDispatch(string isim, T)(T parametre) { 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 opBinary!"in"
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 opBinaryRight!"in" yüklenir ve derleyici de perde arkasında o üye işlevi çağırır:
// üsttekinin eşdeğeri if (öğleTatili.opBinaryRight!"in"(zaman)) {
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.stdio; import std.string; struct Süre { int dakika; } struct GününSaati { int saat; int dakika; void opOpAssign(string işleç)(in Süre süre) if (işleç == "+") { 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 opBinaryRight(string işleç) (const ref GününSaati zaman) const if (işleç == "in") { 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) { writeln(zaman, " öğle tatilinde"); } else { writeln(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
GününSaatiyapısınınopCmpişlevini zamanların ters sırada sıralanmalarını sağlayacak şekilde değiştirin.- 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 birSürekadar sağa ve sola kaydırsınlar: - Bu kullanımı şüpheli olsa da,
~işleciniZamanAralığıveGününSaatinesnelerinin aşağıdaki iki kullanımları için yükleyin:
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.
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.
D.ershane
Forum
Wiki
Projeler
Tanıtım
İletişim
Hakları