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

atama: [assign], değişkene yeni bir değer vermek
değişken: [variable], kavramları temsil eden veya sınıf nesnesine erişim sağlayan program yapısı
dönüş değeri: [return value], işlevin üreterek döndürdüğü değer
fonksiyonel programlama: [functional programming], yan etki üretmeme ilkesine dayalı programlama yöntemi
hevesli: [eager], işlemlerin, ürettikleri sonuçların kullanılacaklarından emin olunmadan gerçekleştirilmeleri
iş parçacığı: [thread], işletim sisteminin program işletme birimi
işlev: [function], programdaki bir kaç adımı bir araya getiren program parçası
nesne: [object], belirli bir sınıf veya yapı türünden olan değişken
parametre: [parameter], işleve işini yapması için verilen bilgi
parametre değeri: [argument], işleve parametre olarak verilen bir değer
referans: [reference], asıl nesneye, onun takma ismi gibi erişim sağlayan program yapısı
sağ değer: [rvalue], adresi alınamayan değer
sol değer: [lvalue], adresi alınabilen değer
tanımsız davranış: [undefined behavior], programın ne yapacağının dil tarafından tanımlanmamış olması
yapı: [struct], başka verileri bir araya getiren veri yapısı
... bütün sözlük



İngilizce Kaynaklar


Diğer




İşlev Parametreleri

Bu bölümde parametrelerin işlevlere gönderilmeleri konusundaki ayrıntıları göreceğiz ve D'deki parametre çeşitlerini tanıyacağız.

Aslında bu bölümün konularının bazılarıyla önceki bölümlerde karşılaşmıştık. Örneğin, foreach Döngüsü bölümünde ref anahtar sözcüğünün elemanların kopyalarını değil, kendilerini kullandırdığını görmüştük.

Ek olarak, hem const ve immutable belirteçlerinin parametrelerle kullanımını hem de değer türleriyle referans türleri arasındaki farkları daha önceki bölümlerde görmüştük.

Önceki programlarda işlevlerin nasıl parametrelerini kullanarak sonuçlar ürettiklerini gördük. Örneğin, hiçbir yan etkisi olmayan ve işi yalnızca değer üretmek olan bir işlev şöyle yazılabiliyordu:

double seneSonuNotu(double vizeNotu, double finalNotu) {
    return vizeNotu * 0.4 + finalNotu * 0.6;
}

O işlevde vize notu ağırlığının %40, final notununkinin de %60 olarak hesaplandığını görebiliyoruz. O işlevi örneğin şu şekilde çağırabiliriz:

    int vizeOrtalaması = 76;
    int finalNotu = 80;

    writefln("Sene sonu notu: %2.0f",
             seneSonuNotu(vizeOrtalaması, finalNotu));
Parametre her zaman kopyalanır

Yukarıdaki kodun vizeOrtalaması ve finalNotu değişkenlerini kullandığını söylediğimizde aslında temelde bir hataya düşmüş oluruz çünkü aslında işlev tarafından kullanılanlar değişkenlerin kendileri değil, kopyalarıdır.

Bu ayrım önemlidir çünkü parametrede yapılan değişiklik ancak kopyayı etkiler. Bunu yan etki üretmeye çalışan aşağıdaki işlevde görebiliriz. Bu işlev bir oyun karakterinin enerjisini azaltmak için yazılmış olsun:

void enerjisiniAzalt(double enerji) {
    enerji /= 4;
}

O işlevi denemek için yazılmış olan şu programa bakalım:

import std.stdio;

void enerjisiniAzalt(double enerji) {
    enerji /= 4;
}

void main() {
    double enerji = 100;

    enerjisiniAzalt(enerji);
    writeln("Yeni enerji: ", enerji);
}

Çıktısı:

Yeni enerji: 100     ← Değişmedi

enerjisiniAzalt işlevi parametresinin değerini dörtte birine düşürdüğü halde main içindeki enerji isimli değişkenin değeri aynı kalmaktadır. Bunun nedeni, main içindeki enerji ile enerjisiniAzalt işlevinin parametresi olan enerji'nin farklı değişkenler olmalarıdır. Parametre, main içindeki değişkenin kopyasıdır.

Bu olayı biraz daha yakından incelemek için programa bazı çıktı ifadeleri yerleştirebiliriz:

import std.stdio;

void enerjisiniAzalt(double enerji) {
    writeln("İşleve girildiğinde       : ", enerji);
    enerji /= 4;
    writeln("İşlevden çıkılırken       : ", enerji);
}

void main() {
    double enerji = 100;

    writeln("İşlevi çağırmadan önce    : ", enerji);
    enerjisiniAzalt(enerji);
    writeln("İşlevden dönüldükten sonra: ", enerji);
}

Çıktısı:

İşlevi çağırmadan önce    : 100
İşleve girildiğinde       : 100
İşlevden çıkılırken       : 25   ← parametre değişir,
İşlevden dönüldükten sonra: 100  ← asıl enerji değişmez

Çıktıdan anlaşıldığı gibi, isimleri aynı olsa da main içindeki enerji ile enerjisiniAzalt içindeki enerji farklı değişkenlerdir. İşleve main içindeki değişkenin değeri kopyalanır ve değişiklik bu kopyayı etkiler.

Bu, ilerideki bölümlerde göreceğimiz yapı nesnelerinde de böyledir: Yapı nesneleri de işlevlere kopyalanarak gönderilirler.

Referans türlerinin eriştirdiği değişkenler kopyalanmazlar

Dilim, eşleme tablosu, ve sınıf gibi referans türleri de işlevlere kopyalanırlar. Ancak, bu türlerin erişim sağladığı değişkenler (dilim ve eşleme tablosu elemanları ve sınıf nesneleri) kopyalanmazlar. Bu çeşit değişkenler işlevlere referans olarak geçirilirler. Parametre, asıl nesneye eriştiren yeni bir reeferanstır ve dolayısıyla, parametrede yapılan değişiklik asıl nesneyi değiştirir.

Dizgiler de dizi olduklarından bu durum onlar için de geçerlidir. Parametresinde değişiklik yapan şu işleve bakalım:

import std.stdio;

void başHarfiniNoktaYap(dchar[] dizgi) {
    dizgi[0] = '.';
}

void main() {
    dchar[] dizgi = "abc"d.dup;
    başHarfiniNoktaYap(dizgi);
    writeln(dizgi);
}

Parametrede yapılan değişiklik main içindeki asıl nesneyi değiştirmiştir:

.bc

Buna rağmen, dilim ve eşleme tablosu değişkenlerinin kendileri yine de kopyalanırlar. Bu durum, parametre özellikle ref belirteci ile tanımlanmamışsa şaşırtıcı sonuçlar doğurabilir.

Dilimlerin şaşırtıcı olabilen referans davranışları

Başka Dizi Olanakları bölümünde belirtildiği gibi, bir dilime eleman eklenmesi paylaşımı sonlandırabilir. Paylaşım sonlanmışsa yukarıdaki dizgi gibi bir parametre artık asıl elemanlara erişim sağlamıyor demektir.

import std.stdio;

void sıfırEkle(int[] dilim) {
    dilim ~= 0;
    writefln("sıfırEkle() içindeyken: %s", dilim);
}

void main() {
    auto dilim = [ 1, 2 ];
    sıfırEkle(dilim);
    writefln("sıfırEkle()'den sonra : %s", dilim);
}

Yeni eleman yalnızca parametreye eklenir, çağıran taraftaki dilime değil:

sıfırEkle() içindeyken: [1, 2, 0]
sıfırEkle()'den sonra : [1, 2]    ← 0 elemanı yok

Yeni elemanların gerçekten de asıl dilime eklenmesi istendiğinde parametrenin ref olarak geçirilmesi gerekir:

void sıfırEkle(ref int[] dilim) {
    // ...
}

ref belirtecini biraz aşağıda göreceğiz.

Eşleme tablolarının şaşırtıcı olabilen referans davranışları

Eşleme tablosu çeşidinden olan parametreler de şaşırtıcı sonuçlar doğurabilirler. Bunun nedeni, eşleme tablolarının yaşamlarına boş olarak değil, null olarak başlamalarıdır.

null, bu anlamda ilklenmemiş eşleme tablosu anlamına gelir. Eşleme tabloları ilk elemanları eklendiğinde otomatik olarak ilklenirler. Bunun bir etkisi olarak, eğer bir işlev null olan bir eşleme tablosuna bir eleman eklerse o eleman çağıran tarafta görülemez çünkü parametre ilklenmiştir ama çağıran taraftaki değişken yine null'dır:

import std.stdio;

void elemanEkle(int[string] tablo) {
    tablo["kırmızı"] = 100;
    writefln("elemanEkle() içindeyken: %s", tablo);
}

void main() {
    int[string] tablo;    // ← null tablo
    elemanEkle(tablo);
    writefln("elemanEkle()'den sonra : %s", tablo);
}

Eklenen eleman çağıran taraftaki tabloya eklenmemiştir:

elemanEkle() içindeyken: ["kırmızı":100]
elemanEkle()'den sonra : []    ← Elemanı yok

Öte yandan, işleve gönderilen tablo null değilse, eklenen eleman o tabloda da görülür:

    int[string] tablo;
    tablo["mavi"] = 10;    // ← Bu sefer null değil
    elemanEkle(tablo);

Bu sefer, eklenen eleman çağıran taraftaki tabloda da görülür:

elemanEkle() içindeyken: ["mavi":10, "kırmızı":100]
elemanEkle()'den sonra : ["mavi":10, "kırmızı":100]

Bu yüzden, eşleme tablolarını da ref parametreler olarak geçirmek daha uygun olabilir.

Parametre çeşitleri

Parametrelerin işlevlere geçirilmeleri normalde yukarıdaki iki temel kurala uyar:

Bunlar bir belirteç kullanılmadığı zaman varsayılan kurallardır. Bu genel kurallar aşağıdaki anahtar sözcükler yardımıyla değiştirilebilir.

in

İşlevlerin değer veya yan etki üretebildiklerini görmüştük. in anahtar sözcüğü parametrenin işlev tarafından yalnızca giriş bilgisi olarak kullanıldığını belirtir. Bu tür parametreler değiştirilemezler. İngilizce'de "içeriye" anlamına gelen "in" parametrenin amacını daha açık ifade eder:

import std.stdio;

double ağırlıklıToplam(in double şimdikiToplam,
                       in double ağırlık,
                       in double eklenecekDeğer) {
    return şimdikiToplam + (ağırlık * eklenecekDeğer);
}

void main() {
    writeln(ağırlıklıToplam(1.23, 4.56, 7.89));
}

in parametreler değiştirilemezler:

void deneme(in int değer) {
    değer = 1;    // ← derleme HATASI
}
out

İşlevin ürettiği bilginin işlevden return anahtar sözcüğü ile döndürüldüğünü görmüştük. İşlevlerden tek değer döndürülebiliyor olması bazen kısıtlayıcı olabilir çünkü bazı işlevlerin birden fazla sonuç üretmesi istenir. (Not: Aslında dönüş türü Tuple veya struct olduğunda işlevler birden fazla değer döndürebilirler. Bu olanakları ilerideki bölümlerde göreceğiz.)

"Dışarıya" anlamına gelen out belirteci, işlevlerin parametreleri yoluyla da sonuç üretmelerini sağlar. İşlev bu çeşit parametrelerin değerlerini atama yoluyla değiştirdiğinde, o değerler işlevi çağıran ortamda da sonuç olarak görülürler. Bilgi bir anlamda işlevden dışarıya gönderilmektedir.

Örnek olarak iki sayıyı bölen ve hem bölümü hem de kalanı üreten bir işleve bakalım. İşlevin dönüş değerini bölmenin sonucu için kullanırsak bölmeden kalanı da bir out parametre olarak döndürebiliriz:

import std.stdio;

int kalanlıBöl(in int bölünen, in int bölen, out int kalan) {
    kalan = bölünen % bölen;
    return bölünen / bölen;
}

void main() {
    int kalan;
    int bölüm = kalanlıBöl(7, 3, kalan);

    writeln("bölüm: ", bölüm, ", kalan: ", kalan);
}

İşlevin kalan isimli parametresinin değiştirilmesi main içindeki kalan'ın değişmesine neden olur (isimlerinin aynı olması gerekmez):

bölüm: 2, kalan: 1

Değerleri çağıran tarafta ne olursa olsun, işleve girildiğinde out parametreler öncelikle türlerinin ilk değerine dönüşürler:

import std.stdio;

void deneme(out int parametre) {
    writeln("İşleve girildiğinde   : ", parametre);
}

void main() {
    int değer = 100;

    writeln("İşlev çağrılmadan önce: ", değer);
    deneme(değer);
    writeln("İşlevden dönüldüğünde : ", değer);
}

O işlevde parametreye hiçbir değer atanmıyor bile olsa işleve girildiğinde parametrenin değeri int'in ilk değeri olmakta ve bu main içindeki değeri de etkilemektedir:

İşlev çağrılmadan önce: 100
İşleve girildiğinde   : 0     ← int.init değerinde
İşlevden dönüldüğünde : 0

Görüldüğü gibi, out parametreler dışarıdan bilgi alamazlar, yalnızca dışarıya bilgi gönderebilirler.

out parametre yerine dönüş türü olarak Tuple veya struct kullanmak daha iyidir. Bunları ilerideki bölümlerde göreceğiz.

const

Daha önce de gördüğümüz gibi, bu belirteç parametrenin işlev içinde değiştirilmeyeceği garantisini verir. Bu sayede, işlevi çağıranlar hem parametrede değişiklik yapılmadığını bilmiş olurlar, hem de işlev const veya immutable olan değişkenlerle de çağrılabilir:

import std.stdio;

dchar sonHarfi(const dchar[] dizgi) {
    return dizgi[$ - 1];
}

void main() {
    writeln(sonHarfi("sabit"));
}
immutable

Daha önce de gördüğümüz gibi, bu belirteç parametrenin programın çalışması süresince değişmemesini şart koşar. Bu konuda ısrarlı olduğundan aşağıdaki işlevi ancak elemanları immutable olan dizgilerle çağırabiliriz (örneğin, dizgi hazır değerleriyle):

import std.stdio;

dchar[] karıştır(immutable dchar[] birinci,
                 immutable dchar[] ikinci) {
    dchar[] sonuç;
    int i;

    for (i = 0; (i < birinci.length) && (i < ikinci.length); ++i) {
        sonuç ~= birinci[i];
        sonuç ~= ikinci[i];
    }

    sonuç ~= birinci[i..$];
    sonuç ~= ikinci[i..$];

    return sonuç;
}

void main() {
    writeln(karıştır("MERHABA", "dünya"));
}

Kısıtlayıcı bir belirteç olduğundan, immutable'ı ancak değişmezliğin gerçekten gerekli olduğu durumlarda kullanmanızı öneririm. Öte yandan, const parametreler genelde daha kullanışlıdır çünkü bunlar const, immutable, ve değişebilen değişkenlerin hepsini kabul ederler.

ref

İşleve normalde kopyalanarak geçirilecek olan bir değişkenin referans olarak geçirilmesini sağlar.

Yukarıda parametresi normalde kopyalandığı için istediğimiz gibi çalışmayan enerjisiniAzalt işlevinin main içindeki asıl değişkeni değiştirebilmesi için parametresini referans olarak alması gerekir:

import std.stdio;

void enerjisiniAzalt(ref double enerji) {
    enerji /= 4;
}

void main() {
    double enerji = 100;

    enerjisiniAzalt(enerji);
    writeln("Yeni enerji: ", enerji);
}

İşlev parametresinde yapılan değişiklik artık main içindeki enerji'nin değerini değiştirir:

Yeni enerji: 25

Görüldüğü gibi, ref parametreler işlev içinde hem kullanılmak üzere giriş bilgisidirler, hem de sonuç üretmek üzere çıkış bilgisidirler. ref parametreler asıl değişkenlerin takma isimleri olarak da düşünülebilirler. Yukarıdaki işlev parametresi olan enerji, main içindeki enerji'nin bir takma ismi gibi işlem görür. ref yoluyla yapılan değişiklik asıl değişkeni değiştirir.

ref parametreler işlevlerin yan etki üreten türden işlevler olmalarına neden olurlar: Dikkat ederseniz, enerjisiniAzalt işlevi değer üretmemekte, parametresinde bir değişiklik yapmaktadır.

Fonksiyonel programlama denen programlama yönteminde yan etkilerin özellikle azaltılmasına çalışılır. Hatta, bazı programlama dillerinde yan etkilere hiç izin verilmez. Değer üreten işlevlerin yan etkisi olan işlevlerden programcılık açısından daha üstün oldukları kabul edilir. İşlevlerinizi olabildiğince değer üretecek şekilde tasarlamanızı öneririm. İşlevlerin yan etkilerini azaltmak, onların daha anlaşılır ve daha kolay olmalarını sağlar.

Aynı işi fonksiyonel programlamaya uygun olacak şekilde gerçekleştirmek için (yani, değer üreten işlev kullanmak için) programı şöyle değiştirmek önerilir:

import std.stdio;

double düşükEnerji(double enerji) {
    return enerji / 4;
}

void main() {
    double enerji = 100;

    enerji = düşükEnerji(enerji);
    writeln("Yeni enerji: ", enerji);
}
auto ref

Bu belirteç yalnızca şablonlarla kullanılabilir. Bir sonraki bölümde göreceğimiz gibi, sol değerler auto ref parametrelere referans olarak, sağ değerler ise kopyalanarak geçirilirler.

inout

İsminin in ve out sözcüklerinden oluştuğuna bakıldığında bu belirtecin hem giriş hem çıkış anlamına geldiği düşünebilir ancak bu doğru değildir. Hem giriş hem çıkış anlamına gelen belirtecin ref olduğunu yukarıda gördük.

inout, parametrenin değişmezlik bilgisini otomatik olarak çıkış değerine taşımaya yarar. Parametre const, immutable, veya değişebilen olduğunda dönüş değeri de const, immutable, veya değişebilen olur.

Bu belirtecin yararını görmek için kendisine verilen dilimin ortadaki elemanlarını yine dilim olarak döndüren bir işleve bakalım:

import std.stdio;

int[] ortadakileri(int[] dilim) {
    if (dilim.length) {
        --dilim.length;               // sondan kırp

        if (dilim.length) {
            dilim = dilim[1 .. $];    // baştan kırp
        }
    }

    return dilim;
}

void main() {
    int[] sayılar = [ 5, 6, 7, 8, 9 ];
    writeln(ortadakileri(sayılar));
}

Çıktısı:

[6, 7, 8]

Kitabın bu noktasına kadar anladıklarımız doğrultusunda bu işlevin parametresinin aslında const(int)[] olarak bildirilmiş olması gerekir çünkü kendisine verilen dilimin elemanlarında değişiklik yapmamaktadır. Dikkat ederseniz, dilimin kendisinin değiştirilmesinde bir sakınca yoktur çünkü değiştirilen dilim işlevin çağrıldığı yerdeki dilim değil, onun kopyasıdır.

Ancak, işlev buna uygun olarak tekrar yazıldığında bir derleme hatası alınır:

int[] ortadakileri(const(int)[] dilim) {
    // ...
    return dilim;    // ← derleme HATASI
}

Derleme hatası, elemanları değiştirilemeyen bir dilimin elemanları değiştirilebilen bir dilim olarak döndürülemeyeceğini bildirir:

Error: cannot implicitly convert expression (dilim) of
type const(int)[] to int[]

Bunun çözümü olarak dönüş türünün de const(int)[] olarak belirlenmesi düşünülebilir:

const(int)[] ortadakileri(const(int)[] dilim) {
    // ...
    return dilim;    // şimdi derlenir
}

Kod, yapılan o değişiklikle derlenir. Ancak, bu sefer ortaya farklı bir kısıtlama çıkmıştır: İşlev değişebilen elemanlardan oluşan bir dilimle bile çağrılmış olsa döndürdüğü dilim const elemanlardan oluşacaktır. Bunun zararını görmek için bir dilimin ortadaki elemanlarının on katlarını almaya çalışan şu koda bakalım:

    int[] ortadakiler = ortadakileri(sayılar); // ← derleme HATASI
    ortadakiler[] *= 10;

İşlevin döndürdüğü const(int)[] türündeki dilimin int[] türündeki dilime atanamaması doğaldır:

Error: cannot implicitly convert expression
(ortadakileri(sayılar)) of type const(int)[] to int[]

Asıl dilim değişebilen elemanlardan oluştuğu halde ortadaki bölümü üzerine böyle bir kısıtlama getirilmesi kullanışsızlıktır. İşte, inout değişmezlikle ilgili olan bu sorunu çözer. Bu anahtar sözcük hem parametrede hem de dönüş türünde kullanılır ve parametrenin değişebilme durumunu dönüş değerine taşır:

inout(int)[] ortadakileri(inout(int)[] dilim) {
    // ...
    return dilim;
}

Aynı işlev artık const, immutable, ve değişebilen dilimlerle çağrılabilir:

    {
        int[] sayılar = [ 5, 6, 7, 8, 9 ];
        // Dönüş türü değişebilen elemanlı dilimdir
        int[] ortadakiler = ortadakileri(sayılar);
        ortadakiler[] *= 10;
        writeln(ortadakiler);
    }

    {
        immutable int[] sayılar = [ 10, 11, 12 ];
        // Dönüş türü immutable elemanlı dilimdir
        immutable int[] ortadakiler = ortadakileri(sayılar);
        writeln(ortadakiler);
    }

    {
        const int[] sayılar = [ 13, 14, 15, 16 ];
        // Dönüş türü const elemanlı dilimdir
        const int[] ortadakiler = ortadakileri(sayılar);
        writeln(ortadakiler);
    }
lazy

Doğal olarak, parametre değerleri işlevler çağrılmadan önce işletilirler. Örneğin, topla gibi bir işlevi başka iki işlevin sonucu ile çağırdığımızı düşünelim:

    sonuç = topla(birMiktar(), başkaBirMiktar());

topla'nın çağrılabilmesi için öncelikle birMiktar ve başkaBirMiktar işlevlerinin çağrılmaları gerekir çünkü topla hangi iki değeri toplayacağını bilmek zorundadır.

İşlemlerin bu şekilde doğal olarak işletilmeleri hevesli olarak tanımlanır. Program, işlemleri öncelik sıralarına göre hevesle işletir.

Oysa bazı parametreler işlevin işleyişine bağlı olarak belki de hiçbir zaman kullanılmayacaklardır. Parametre değerlerinin hevesli olarak önceden işletilmeleri kullanılmayan parametrelerin gereksiz yere hesaplanmış olmalarına neden olacaktır.

Bunun bilinen bir örneği, programın işleyişiyle ilgili mesajlar yazdırmaya yarayan log işlevleridir. Bu işlevler kullanıcı ayarlarına bağlı olarak yalnızca yeterince öneme sahip olan mesajları yazdırırlar:

enum Önem { düşük, orta, yüksek }
// Not: Önem, İngilizce'de 'log level' olarak bilinir.

void logla(Önem önem, string mesaj) {
    if (önem >= önemAyarı) {
        writefln("%s", mesaj);
    }
}

Örneğin, eğer kullanıcı yalnızca Önem.yüksek değerli mesajlarla ilgileniyorsa, Önem.orta değerindeki mesajlar yazdırılmazlar. Buna rağmen, parametre değeri işlev çağrılmadan önce yine de hesaplanacaktır. Örneğin, aşağıdaki mesajı oluşturan format ifadesinin tamamı (bağlantıDurumunuÖğren() çağrısı dahil) logla işlevi çağrılmadan önce işletilecek ama bu işlem mesaj yazdırılmadığı zaman boşa gitmiş olacaktır:

    if (!bağlanıldı_mı) {
        logla(Önem.orta,
              format("Hata. Bağlantı durumu: '%s'.",
                     bağlantıDurumunuÖğren()));
    }

lazy anahtar sözcüğü parametreyi oluşturan ifadenin yalnızca o parametre işlev içinde gerçekten kullanıldığında (ve her kullanıldığında) hesaplanacağını bildirir:

void logla(Önem önem, lazy string mesaj) {
   // ... işlevin tanımı öncekiyle aynı ...
}

Artık ifade mesaj gerçekten kullanıldığında hesaplanır.

Dikkat edilmesi gereken bir nokta, lazy parametrenin değerinin o parametre her kullanıldığında hesaplanacağıdır.

Örneğin, aşağıdaki işlevin lazy parametresi üç kere kullanıldığından onu hesaplayan işlev de üç kere çağrılmaktadır:

import std.stdio;

int parametreyiHesaplayanİşlev() {
    writeln("Hesap yapılıyor");
    return 1;
}

void tembelParametreliİşlev(lazy int değer) {
    int sonuç = değer + değer + değer;
    writeln(sonuç);
}

void main() {
    tembelParametreliİşlev(parametreyiHesaplayanİşlev());
}

Çıktısı:

Hesap yapılıyor
Hesap yapılıyor
Hesap yapılıyor
3

lazy belirtecini değeri ancak bazı durumlarda kullanılan parametreleri belirlemek için kullanabilirsiniz. Ancak, değerin birden fazla sayıda hesaplanabileceğini de unutmamak gerekir.

scope

Bu anahtar sözcük, parametrenin işlev tarafından bir kenara kaydedilmeyeceğini bildirir. Bir anlamda, işlevin o parametreyle işinin kısa olacağını garanti eder:

int[] modülDilimi;

int[] işlev(scope int[] parametre) {
    modülDilimi = parametre;    // ← derleme HATASI
    return parametre;           // ← derleme HATASI
}

void main() {
    int[] dilim = [ 10, 20 ];
    int[] sonuç = işlev(dilim);
}

O işlev scope ile verdiği sözü iki yerde bozmaktadır çünkü onu hem modül kapsamındaki bir dilime atamakta hem de dönüş değeri olarak kullanmaktadır. Bu davranışların ikisi de parametrenin işlevin sonlanmasından sonra da kullanılabilmelerine neden olacağından derleme hatasına neden olur.

shared

Bu anahtar sözcük parametrenin iş parçacıkları arasında paylaşılabilen çeşitten olmasını gerektirir:

void işlev(shared int[] i) {
    // ...
}

void main() {
    int[] sayılar = [ 10, 20 ];
    işlev(sayılar);    // ← derleme HATASI
}

Yukarıdaki program derlenemez çünkü parametre olarak kullanılan değişken shared değildir. Program aşağıdaki değişiklikle derlenebilir:

    shared int[] sayılar = [ 10, 20 ];
    işlev(sayılar);    // şimdi derlenir

shared anahtar sözcüğünü ilerideki Veri Paylaşarak Eş Zamanlı Programlama bölümünde kullanacağız.

return

Bazı durumlarda bir işlevin ref parametrelerinden birisini doğrudan döndürmesi istenebilir. Örneğin, aşağıdaki seç() işlevi rasgele seçtiği bir parametresini döndürmekte ve böylece çağıran taraftaki bir değişkenin doğrudan değiştirilmesi sağlanmaktadır:

import std.stdio;
import std.random;

ref int seç(ref int soldaki, ref int sağdaki) {
    return uniform(0, 2) ? soldaki : sağdaki;
}

void main() {
    int a;
    int b;

    seç(a, b) = 42;

    writefln("a: %s, b: %s", a, b);
}

Sonuçta main() içindeki a veya b değişkenlerinden birisinin değeri 42 olur:

a: 42, b: 0
a: 0, b: 42

Ancak, bazı durumlarda seç()'e gönderilen parametrelerin yaşam süreçleri döndürülen referansın yaşam sürecinden daha kısa olabilir. Örneğin, aşağıdaki foo() işlevi seç()'i iki yerel değişkenle çağırmakta ve sonuçta kendisi bunlardan birisine referans döndürmüş olmaktadır:

import std.random;

ref int seç(ref int soldaki, ref int sağdaki) {
    return uniform(0, 2) ? soldaki : sağdaki;
}

ref int foo() {
    int a;
    int b;

    return seç(a, b);    // ← HATA: geçersiz referans döndürülüyor
}

void main() {
    foo() = 42;          // ← HATA: yasal olmayan adrese yazılıyor
}

a ve b değişkenlerinin yaşam süreçleri foo()'dan çıkıldığında sona erdiğinden, main() içindeki atama işlemi yasal bir değişkene yapılamaz. Bu, tanımsız davranıştır.

Tanımsız davranış, programın davranışının programlama dili tarafından belirlenmediğini ifade eder. Tanımsız davranış içeren bir programın davranışı hakkında hiçbir şey söylenemez. (Olasılıkla, 42 değeri daha önceden a veya b için kullanılan ama belki de artık ilgisiz bir değişkene ait olan bir bellek bölgesine yazılacak ve o değişkenin değerini önceden kestirilemeyecek biçimde bozacaktır.)

Parametreye uygulanan return anahtar sözcüğü böyle hataları önler. return, o parametrenin döndürülen referanstan daha uzun yaşayan bir değişken olması gerektiğini bildirir:

import std.random;

ref int seç(return ref int soldaki, return ref int sağdaki) {
    return uniform(0, 2) ? soldaki : sağdaki;
}

ref int foo() {
    int a;
    int b;

    return seç(a, b);    // ← derleme HATASI
}

void main() {
    foo();
}

Derleyici bu sefer seç()'e gönderilen değişkenlerin foo()'nun döndürmeye çalıştığı referanstan daha kısa yaşadıklarını farkeder ve yerel değişkene referans döndürülmekte olduğunu bildiren bir hata verir:

Error: escaping reference to local variable a
Error: escaping reference to local variable b

Not: Derleyicinin böyle bir hatayı return anahtar sözcüğüne gerek kalmadan da görmüş olabileceği düşünülebilir. Ancak, bu her durumda mümkün değildir çünkü derleyici her derleme sırasında her işlevin içeriğini görmüyor olabilir.

Özet
Problem

Aşağıdaki işlev kendisine verilen iki parametrenin değerlerini değiş tokuş etmeye çalışmaktadır:

import std.stdio;

void değişTokuş(int birinci, int ikinci) {
    int geçici = birinci;
    birinci = ikinci;
    ikinci = geçici;
}

void main() {
    int birSayı = 1;
    int başkaSayı = 2;

    değişTokuş(birSayı, başkaSayı);

    writeln(birSayı, ' ', başkaSayı);
}

Ancak, işlev istendiği gibi çalışmamaktadır:

1 2          ← değiş tokuş olmamış

İşlevi düzeltin ve değişkenlerin değerlerinin değiş tokuş edilmelerini sağlayın.