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

betik: [script], terminal komutlarından oluşan program
dönüş değeri: [return value], işlevin üreterek döndürdüğü değer
gösterge: [pointer], bir değişkeni gösteren değişken
işlev: [function], programdaki bir kaç adımı bir araya getiren program parçası
klasör: [directory], dosyaları barındıran dosya sistemi yapısı, "dizin"
modül: [module], programın veya kütüphanenin işlev ve tür tanımlarından oluşan bir alt birimi
ortam değişkeni: [environment variable], programı başlatan ortamın sunduğu PATH gibi değişken
parametre: [parameter], işleve işini yapması için verilen bilgi
Phobos: [Phobos], D dilinin standart kütüphanesi
uç birim: [terminal], bilgisayar sistemlerinin kullanıcıyla etkileşen giriş/çıkış birimi; "konsol", "komut satırı", "cmd penceresi", "DOS ekranı", vs.
... bütün sözlük



İngilizce Kaynaklar


Diğer




Programın Çevresiyle Etkileşimi

İşlevleri anlatırken main'in de bir işlev olduğunu söylemiştim. Programın işleyişi main'le başlar ve oradan başka işlevlere dallanır. main'in şimdiye kadar gördüğümüz tanımı şöyleydi:

void main() {
    // ...
}

O tanıma bakarak main'in bir değer döndürmediğini ve hiçbir parametre almadığını düşünürüz. Aslında bu mümkün değildir, çünkü programı başlatan ortam bir dönüş değeri bekler; main, void döndürüyor gibi yazılmış olsa da aslında bir değer döndürür.

main'in dönüş değeri

Programlar her zaman için başka bir ortam tarafından başlatılırlar. Bu ortam, programı ismini yazıp Enter'a basarak başlattığımız uç birim olabilir, menülerindeki "Çalıştır" gibi bir komutla başlattığımız bir geliştirme ortamı olabilir, programı kendisi başlatan başka bir program olabilir, vs.

Program kendisini başlatan bu ortama işini başarıyla tamamlayıp tamamlamadığı bilgisini main'in dönüş değeri olarak bildirir.

Programın dönüş değeri olarak 0 değeri programın başarıyla sonuçlandığını, 0'dan başka bir değer ise programın çalışması sırasında bir hata oluştuğunu bildirmek için kullanılır. İstediğimiz değeri döndürmek bize kalmış olsa da, 0'ın başarı anlamına gelmesi standartlaşmıştır.

Not: Dönüş değeri olarak ancak [0,127] aralığındaki tamsayılara güvenebilirsiniz. Bunun dışındaki değerler her ortam tarafından desteklenmiyor olabilir.

Sıfırdan başka değerler her programa göre değişik anlamlar taşıyabilir. Örneğin Unix türevi ortamlarda dosyaları listelemek için kullanılan ls programı önemsiz hatalarda 1 değerini, ciddi hatalarda ise 2 değerini döndürür. Komut satırından başlatılan programların dönüş değerleri $? ortam değişkeninden okunabilir. Örneğin klasörde bulunmayan bir dosyayı görmek istediğimizde, programın dönüş değeri komut satırında $? değişkeninden aşağıdaki gibi okunabilir.

Not: Aşağıdaki komut satırı örneklerinde # karakteriyle başlayan satırlar kullanıcın yazdığı satırları gösteriyor. Aynı adımları denemek istediğinizde o satırları # karakteri dışında sizin yazarak Enter'a basmanız gerekir. O satırları daha koyu olarak gösterdim.

Ek olarak, aşağıdaki örnekler bir Linux ortamında denenmiş olsalar da, benzerlerini örneğin Windows DOS pencerelerinde de kullanabilirsiniz.

# ls klasorde_bulunmayan_bir_dosya
ls: klasorde_bulunmayan_bir_dosya: No such file or directory
# echo $?
2      ← ls'in dönüş değeri
Dönüş değeri void olan main'ler de değer üretirler

Şimdiye kadar karşılaştığımız işlevlerin bazılarının işlerini yapamayacakları durumlara düştüklerinde hata attıklarını görmüştük. Şimdiye kadar gördüğümüz kadarıyla, hata atıldığı zaman program bir object.Exception mesajıyla sonlanıyordu.

Bu gibi durumlarda, main'in dönüş türü olarak void kullanılmış olsa bile, yani main bir değer döndürmeyecekmiş gibi yazılmış olsa bile, programı çalıştıran ortama otomatik olarak 1 değeri döndürülür. Bunu görmek için hata atarak sonlanan şu basit programı çalıştıralım:

void main() {
    throw new Exception("Bir hata oldu");
}

Dönüş türü void olarak tanımlandığı halde programı çalıştıran ortama 1 değeri döndürülmüştür:

# ./deneme
object.Exception: Bir hata oldu
# echo $?
1

Benzer şekilde, dönüş türü void olarak tanımlanmış olan main işlevleri başarıyla sonlandıklarında, otomatik olarak dönüş değeri olarak 0 üretirler. Bu sefer başarıyla sonlanan şu programa bakalım:

import std.stdio;

void main() {
    writeln("Başardım!");
}

Bu program dönüş değeri olarak 0 üretmiştir:

# ./deneme
Başardım!
# echo $?
0
Dönüş değerini belirlemek

Kendi programlarımızdan değer döndürmek, başka işlevlerde de olduğu gibi, main'i dönüş türünü int olarak tanımlamak ve bir return deyimi kullanmak kadar basittir:

import std.stdio;

int main() {
    int sayı;
    write("Lütfen 3-6 arasında bir sayı giriniz: ");
    readf(" %s", &sayı);

    if ((sayı < 3) || (sayı > 6)) {
        stderr.writeln("HATA: ", sayı, " uygun değil!");
        return 111;
    }

    writeln("Teşekkür: ", sayı);

    return 0;
}

Programın isminin deneme olduğunu kabul edersek ve istenen aralıkta bir sayı verildiğinde programın dönüş değeri 0 olur:

# ./deneme
Lütfen 3-6 arasında bir sayı giriniz: 5
Teşekkür: 5
# echo $?
0

Aralığın dışında bir sayı verildiğinde ise programın dönüş değeri 111 olur:

# ./deneme
Lütfen 3-6 arasında bir sayı giriniz: 10
HATA: 10 uygun değil!
# echo $?
111

Normalde hata için 1 değerini kullanmak yeterlidir. Ben yalnızca örnek olması için özel bir nedeni olmadan 111 değerini seçtim.

Standart hata akımı stderr

Yukarıdaki programda stderr akımını kullandım. Bu akım, standart akımların üçüncüsüdür ve programın hata mesajlarını yazmak için kullanılır:

Programlar uç birimde başlatıldıklarında stdout ve stderr akımlarına yazılanlar normalde ekranda belirirler. Bu akımlara yazılan mesajları istendiğinde ayrı ayrı elde etmek de mümkündür.

main'in parametreleri

Bazı programlar kendilerini başlatan ortamlardan parametre alabilirler. Örneğin yukarıda gördüğümüz ls programı parametresiz olarak yalnızca ls yazarak başlatılabilir:

# ls
deneme
deneme.d

İsteğe bağlı olarak da bir veya daha çok parametreyle başlatılabilir. Bu parametrelerin anlamları bütünüyle programa bağlıdır ve o programın belgelerinde belirtilmiştir:

# ls -l deneme
-rwxr-xr-x 1 acehreli users 460668 Nov  6 20:38 deneme

D programlarını başlatırken kullanılan parametreler main'e bir string dizisi olarak gönderilirler. main'i string[] parametre alacak şekilde tanımlamak bu parametrelere erişmek için yeterlidir. Aşağıdaki program kendisine verilen parametreleri çıkışına yazdırıyor:

import std.stdio;

void main(string[] parametreler) {
    foreach (i, parametre; parametreler) {
        writefln("%3s numaralı parametre: %s", i, parametre);
    }
}

Rasgele parametrelerle şöyle başlatılabilir:

# ./deneme komut satirina yazilan parametreler 42 --bir_secenek
  0 numaralı parametre: ./deneme
  1 numaralı parametre: komut
  2 numaralı parametre: satirina
  3 numaralı parametre: yazilan
  4 numaralı parametre: parametreler
  5 numaralı parametre: 42
  6 numaralı parametre: --bir_secenek

Parametre dizisinin ilk elemanı her zaman için program başlatılırken kullanılan program ismidir. Diğer parametreler bu dizinin geri kalan elemanlarıdır.

Bu parametrelerle ne yapacağı tamamen programa kalmıştır. Örneğin kendisine verilen iki sözcüğü ters sırada yazdıran bir program:

import std.stdio;

int main(string[] parametreler) {
    if (parametreler.length != 3) {
        stderr.writeln("HATA! Doğru kullanım:\n    ",
                       parametreler[0],
                       " bir_sözcük başka_sözcük");
        return 1;
    }

    writeln(parametreler[2], ' ', parametreler[1]);

    return 0;
}

Bu program gerektiğinde doğru kullanımını da gösteriyor:

# ./deneme
HATA! Doğru kullanım:
    ./deneme bir_sözcük başka_sözcük
# ./deneme dünya merhaba
merhaba dünya
Program seçenekleri ve std.getopt modülü

main'in parametreleriyle ve dönüş değeriyle ilgili olarak bilinmesi gerekenler aslında bu kadardır. Ancak parametreleri teker teker listeden ayıklamak zahmetli olabilir. Onun yerine, bu konuda yardım alabileceğimiz std.getopt modülünün bir kullanımını göstereceğim.

Bazı parametreler program tarafından bilgi olarak kullanılırlar. Örneğin yukarıdaki programa verilen "dünya" ve "merhaba" parametreleri, o programın ekrana yazdıracağı bilgiyi belirliyordu.

Bazı parametreler ise programın işini nasıl yapacağını belirlerler; bunlara program seçeneği denir. Örneğin yukarıda kullandığımız ls programına komut satırında seçenek olarak -l vermiştik.

Program seçenekleri programların kullanışlılıklarını arttırırlar. Böylece, programın istediği parametreler bir insan tarafından teker teker komut satırından yazılmak yerine örneğin bir betik program içinden verilebilirler.

Komut satırı parametrelerinin ne oldukları ve ne anlama geldikleri her ne kadar tamamen programa bağlı olsalar da onların da bir standardı gelişmiştir. POSIX standardına uygun bir kullanımda, her seçenek -- ile başlar, seçenek ismi bunlara bitişik olarak yazılır, ve seçenek değeri de bir = karakterinden sonra gelir:

# ./deneme --bir_secenek=17

Phobos'un std.getopt modülü, programa parametre olarak verilen bu tür seçeneklerin ayıklanmasında yardımcı olur. Ben burada fazla ayrıntısına girmeden getopt işlevinin kısa bir kullanımını göstereceğim.

Çıkışına rasgele seçtiği sayıları yazdıran bir program tasarlayalım. Kaç tane sayı yazdıracağı ve sayıların değerlerinin hangi aralıkta olacağı programa komut satırından seçenekler olarak verilsin. Program örneğin şu şekilde başlatılabilsin:

# ./deneme --adet=7 --en-kucuk=10 --en-buyuk=15

getopt işlevi bu değerleri program parametreleri arasında bulmakta yararlıdır. getopt'un okuduğu değerlerin hangi değişkenlere yazılacakları readf kullanımından tanıdığımız & karakteriyle bir gösterge olarak bildirilir:

import std.stdio;
import std.getopt;
import std.random;

void main(string[] parametreler) {
    int adet;
    int enKüçükDeğer;
    int enBüyükDeğer;

    getopt(parametreler,
           "adet", &adet,
           "en-kucuk", &enKüçükDeğer,
           "en-buyuk", &enBüyükDeğer);

    foreach (i; 0 .. adet) {
        write(uniform(enKüçükDeğer, enBüyükDeğer + 1), ' ');
    }

    writeln();
}
# ./deneme --adet=7 --en-kucuk=10 --en-buyuk=15
11 11 13 11 14 15 10

Çoğu zaman parametrelerin kestirmeleri de olur. Örneğin --adet yazmak yerine kısaca -a yazılır. Seçeneklerin kestirmeleri getopt'a | ayracından sonra bildirilir:

    getopt(parametreler,
           "adet|a", &adet,
           "en-kucuk|k", &enKüçükDeğer,
           "en-buyuk|b", &enBüyükDeğer);

Çoğu zaman kestirme seçenekler için = karakteri de kullanılmaz:

# ./deneme -a7 -k10 -b15
11 13 10 15 14 15 14

Parametrelerin programa string türünde geldiklerini görmüştük. getopt bu dizgileri değişkenlerin türlerine otomatik olarak dönüştürür. Örneğin yukarıdaki programdaki adet'in int olduğunu bildiği için, onu string'den int'e çevirir. getopt'u kullanmadığımız zamanlarda bu dönüşümü daha önce de bir kaç kere kullandığımız to işleviyle kendimiz de gerçekleştirebiliriz.

std.conv modülünde tanımlanmış olan to'yu daha önce hep tamsayıları string'e dönüştürmek için to!string şeklinde kullanmıştık. string yerine başka türlere de dönüştürebiliriz. Örneğin 0'dan başlayarak kendisine komut satırından bildirilen sayıda ikişer ikişer sayan bir programda to'yu şöyle kullanabiliriz:

import std.stdio;
import std.conv;

void main(string[] parametreler) {
    // Parametre verilmediğinde 10 varsayıyoruz
    size_t adet = 10;

    if (parametreler.length > 1) {
        // Bir parametre verilmiş
        adet = to!size_t(parametreler[1]);
    }

    foreach (i; 0 .. adet) {
        write(i * 2, ' ');
    }

    writeln();
}

Program parametre verilmediğinde 10, verildiğinde ise belirtildiği kadar sayı üretir:

# ./deneme
0 2 4 6 8 10 12 14 16 18
# ./deneme 3
0 2 4
Ortam değişkenleri

Programları başlatan ortamlar programların yararlanmaları için ortam değişkenleri de sunarlar. Bu değişkenlere std.process modülündeki environment topluluğu ile eşleme tablosu arayüzü ile erişilir. Örneğin, çalıştırılacak olan programların hangi klasörlerde arandıklarını belirten PATH değişkeni aşağıdaki gibi yazdırılabilir:

import std.stdio;
import std.process;

void main() {
    writeln(environment["PATH"]);
}

Çıktısı:

# ./deneme
/usr/local/bin:/usr/bin

std.process.environment bütün ortam değişkenlerini eşleme tablolarının söz dizimiyle sunar:

import std.process;
// ...
    writeln(environment["PATH"]);

Buna rağmen kendisi bir eşleme tablosu değildir. Bütün değişkenleri tek eşleme tablosu olarak elde etmek için:

    string[string] hepsi = environment.toAA();
Başka programları başlatmak

Programlar başka programları başlattıklarında onların ortamları haline gelirler. Program başlatmaya yarayan işlevler std.process modülünün olanakları rasındadır.

Örneğin, executeShell kendisine parametre olarak verilen dizgiyi sanki komut satırında yazılmış gibi başlatır ve hem programın dönüş değerini hem de çıktısını bir çokuzlu olarak döndürür. Diziye benzer biçimde kullanılan çokuzluları daha sonra Çokuzlular bölümünde göreceğiz:

import std.stdio;
import std.process;

void main() {
    const sonuç = executeShell("ls -l deneme");
    const dönüşDeğeri = sonuç[0];
    const çıktısı = sonuç[1];

    writefln("ls programı %s değerini döndürdü.", dönüşDeğeri);
    writefln("Çıktısı:\n%s", çıktısı);
}

Çıktısı:

# ./deneme
ls programı 0 değerini döndürdü.
Çıktısı:
-rwxrwxr-x. 1 acehreli acehreli 1352810 Oct  6 15:00 deneme

Özet
Problemler
  1. Komut satırından parametre olarak iki değer ve bir işlem karakteri alan bir hesap makinesi yazın. Şöyle çalışsın:
    # ./deneme 3.4 x 7.8
    26.52
    

    Not: * karakterinin komut satırında özel bir anlamı olduğu için çarpma işlemi için x karakterini kullanın. Yine de * karakterini kullanmak isterseniz komut satırında \* şeklinde yazmanız gerekir.

  2. Hangi programı başlatmak istediğinizi soran, bu programı başlatan ve çıktısını yazdıran bir program yazın.