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:
stdin
: standart giriş akımıstdout
: standart çıkış akımıstderr
: standart hata akımı
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
- Dönüş türü
void
olarak tanımlansa bilemain
başarıyla sonlandığında 0, hata ile sonlandığında 1 döndürür. stderr
hata mesajlarını yazmaya uygun olan akımdır.main
,string[]
türünde parametre alabilir.std.getopt
modülü program parametrelerini ayrıştırmaya yarar.std.process
modülü ortam değişkenlerine eriştirmeye ve başka programları başlatmaya yarar.
Problemler
- 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çinx
karakterini kullanın. Yine de*
karakterini kullanmak isterseniz komut satırında\*
şeklinde yazmanız gerekir. - Hangi programı başlatmak istediğinizi soran, bu programı başlatan ve çıktısını yazdıran bir program yazın.