İş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:
- Değer türleri kopyalanırlar. Asıl değişken ve parametre birbirlerinden bağımsızdır.
- Referans türleri de kopyalanırlar ama referans türlerinin doğalarına uygun olarak hem asıl değişken hem de parametre aynı nesneye erişim sağlar.
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
İngilizce'de "içeriye" anlamına gelen in
parametreler, normalde const
parametrelerle aynıdırlar; değiştirilemezler:
void deneme(in int değer) { değer = 1; // ← derleme HATASI }
-preview=in
derleyici seçeneği kullanıldığında ise, in
parametreler programcının amacını "bu işlev bu parametreyi yalnızca giriş verisi olarak kullanacaktır" olarak belirler:
$ dmd -preview=in deneme.d
-preview=in
, derleyicinin in
parametreleri daha uygun olarak geçirmesini sağlar:
in
'in anlamıconst scope
olarak değişir (scope
için aşağıya bakınız)ref
parametrelerin aksine, sağ değerler bilein
parametrelere geçirilebilirler (ref
için aşağıya, sağ değerler için bir sonraki bölüme bakınız)- Kopyalandıklarında yan etki üretecek olan türler (örneğin,
this(this)
işlevi tanımlanmış olan bir tür) veya kopyalanmaları engellenmiş olan türler (örneğinthis(this)
işlevi@disable
ile etkisizleştirilmiş olan bir tür) referans olarak geçirilirler
-preview=in
'in kullanılıp kullanılmamasından bağımsız olarak const
yerine in
parametreler kullanmanızı öneririm.
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(int bölünen, 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
const
yerine in
parametreler kullanmanızı öneririm.
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.
Sağ değerler (bir sonraki bölüme bakınız) ref
parametre olarak geçirilemezler.
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ı) { writeln(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 sonlandıktan sonra kullanılmayacağını bildirir. Bu bölümün yazıldığı sırada scope
ancak işlev @safe
olarak tanımlanmışsa ve -dip1000
derleyici seçeneği kullanılmışsa etkiliydi. DIP, "D Geliştirme Önerisi" anlamına gelen D Improvement Proposal teriminin kısaltmasıdır. DIP 1000 bu bölüm yazıldığında henüz deneme aşamasında olduğundan her durumda istendiği gibi çalışmayabilir.
$ dmd -dip1000 deneme.d
int[] modülDilimi; @safe 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); }
Yukarıdaki işlev scope
ile verdiği sözü iki yerde bozmaktadır: Onu hem modül kapsamındaki bir dilime atamakta hem de dönüş değeri olarak kullanmaktadır. Bu davranışlar parametrenin işlevin sonlanmasından sonra da kullanılabilmesine 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; // ← derleme HATASI } 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; // ← derleme HATASI } 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
- Parametre, işlevin işi için kullanılan bilgidir.
- Parametre değeri, işleve parametre olarak gönderilen bir ifadedir (örneğin bir değişken).
- Her parametre kopyalanarak gönderilir. Ancak, referans türlerinde kopyalanan asıl değişken değil, referansın kendisidir.
in
, parametrenin yalnızca bilgi girişi için kullanıldığını bildirir.const
yerinein
'i öneririm.out
, parametrenin yalnızca bilgi çıkışı için kullanıldığını bildirir.ref
, parametrenin hem bilgi girişi hem de bilgi çıkışı için kullanıldığını bildirir.auto ref
yalnızca şablonlarla kullanılır. Sol değerlerin referans olarak, sağ değerlerin ise kopyalanarak geçirileceğini bildirir.const
, parametrenin işlev içinde değiştirilmediğini garanti eder. (Hatırlarsanız,const
geçişlidir: böyle bir değişken aracılığıyla erişilen başka veriler de değiştirilemez.)const
yerinein
'i öneririm.immutable
, parametre olarak kullanılan değişkeninimmutable
olması şartını getirir.inout
hem paremetrede hem de dönüş türünde kullanılır ve parametreninconst
,immutable
, veya değişebilme özelliğini dönüş türüne taşır.lazy
, parametre olarak gönderilen ifadenin değerinin ancak o değer gerçekten kullanıldığında (ve her kullanıldığında) işletilmesini sağlar.scope
, parametreye eriştiren herhangi bir referansın işlevden dışarıya sızdırılmayacağını bildirir.shared
, parametre olarak kullanılan değişkeninshared
olması şartını getirir.return
, parametrenin döndürülen referanstan daha uzun yaşaması gerektiğini bildirir.
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.